NGINX 上的限流(译)

Posted YP小站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NGINX 上的限流(译)相关的知识,希望对你有一定的参考价值。

前言

本文是对Rate Limiting with nginx and NGINX Plus的主要内容(去掉了关于NGINX Plus相关内容)的翻译。

限流(rate limiting)是NGINX众多特性中最有用的,也是经常容易被误解和错误配置的,特性之一。该特性可以限制某个用户在一个给定时间段内能够产生的HTTP请求数。请求可以简单到就是一个对于主页的GET请求或者一个登陆表格的POST请求。

限流 也可以用于安全目的上,比如减慢暴力密码破解攻击。通过限制进来的请求速率,并且(结合日志)标记出目标URLs来帮助防范DDoS攻击。一般地说,限流是用在保护上游应用服务器不被在同一时刻的大量用户请求湮没。

下面介绍NGINX限流的基本用法。

NGINX限流是如何工作的

NGINX 限流使用漏桶算法(leaky bucket algorithm),该算法广泛应用于通信和基于包交换计算机网络中,用来处理当带宽被限制时的突发情况。和一个从上面进水,从下面漏水的桶的原理很相似;如果进水的速率大于漏水的速率,这个桶就会发生溢出。

在请求处理过程中,水代表从客户端来的请求,而桶代表了一个队列,请求在该队列中依据先进先出(FIFO)算法等待被处理。漏的水代表请求离开缓冲区并被服务器处理,溢出代表了请求被丢弃并且永不被服务。

配置基本的限流功能

有两个主要的指令可以用来配置限流:limit_req_zonelimit_req,例子:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

server {
location /login/ {
limit_req zone=mylimit;


proxy_pass http://my_upstream;
}
}

当limit_req 在它出现的环境中启用了限流(在上面的例子中,作用在所有对于/login/的请求上),则limit_req_zone指令定义了限流的参数。

limit_req_zone指令一般定义在http块内部,使得该指令可以在多个环境中使用。该指令有下面三个参数:

  • Rate — 设置最大的请求速率。在上面的例子中,速率不能超过10个请求每秒。NGINX事实上可以在毫秒级别追踪请求,因此这个限制对应了1个请求每100毫秒。因为我们不允许突刺(bursts,短时间内的突发流量,详细见下一部分。),这意味着如果某个请求到达的时间离前一个被允许的请求小于100毫秒,它会被拒绝。

limit_req_zone指令设置限流和共享内存区域的参数,但是该指令实际上并不限制请求速率。为了限制起作用,需要将该限制应用到某个特定的locationserver块(block),通过包含一个limit_req指令的方式。在上面的例子中,我们将请求限制在/login/上。

处理流量突刺(Bursts)

如果在100毫秒内得到2个请求会怎么样?对于第2个请求,NGINX返回503状态码给客户端。这可能不是我们想要的,因为事实上,应用是趋向于突发性的。相反,我们想要缓存任何过多的请求并且及时地服务它们。下面是我们使用limit_reqburst参数来更新配置:

location /login/ {
limit_req zone=mylimit burst=20;

proxy_pass http://my_upstream;
}

burst参数定义了一个客户端能够产生超出区域(zone)规定的速率的请求数量(在我们示例mylimit区域中,速率限制是10个请求每秒,或1个请求每100毫秒)。一个请求在前一个请求后的100毫秒间隔内达到,该请求会被放入一个队列,并且该队列大小被设置为20.

无延迟排队

带有burst的配置产生平滑的网络流量,但是不实用,因为该配置会使得你的网站表现的很慢。在上面的例子中,队列中第20个数据包等待2秒才能被转发,这时该数据包的响应可能对于客户端已经没有了意义。为了处理这种情况,除了burst参数外,添加nodelay参数。

location /login/ {
limit_req zone=mylimit burst=20 nodelay;

proxy_pass http://my_upstream
}

带有nodelay参数,NGINX仍然会按照burst参数在队列中分配插槽(slot)以及利用已配置的限流,但是不是通过间隔地转发队列中的请求。相反,当某个请求来的太快,只要队列中有可用的空间(slot),NGINX会立即转发它。该插槽(slot)被标记为“已使用”,并且不会被释放给另一个请求,一直到经过适当的时间(在上面的例子中,是100毫秒)。

现在假设在转发第一个请求集合之后的101毫秒,有另外的20个请求同时地到达。队列中只有1个插槽被释放,因此NGINX转发1个请求,并且用503状态拒绝其它的19个请求。相反,如果在这20个新请求到达之前过去了501毫秒,则有5个插槽被释放,因此NGINX立即转发5个请求,并且拒绝其它15个请求。

效果等同于10个请求每秒的限流。如果你想利用请求之间的无限制性间隔的限流,nodelay选项则是非常有用的。

注意:对于大多数的部署,我们推荐在limit_req指令中包含burstnodelay参数。

高级设置的例子

通过结合基本的限流和其它的NGINX特性,你可以实现更多的细微的流量限制。

白名单

下面的例子展示了如何将限流作用在任何一个不在“白名单”中的请求上。

geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/24 0;
}

map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}

limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;

server {
location / {
limit_req zone=req_zone burst=10 nodelay;

# ...
}
}
  • 如果 $limit0$limit_key被设置为空字符串
  • 如果 $limit1$limit_key被设置为客户端的IP地址的二进制格式

limit_req指令将限制作用在/定位中,并且允许在没有转发延迟的情况下,转发多达10个数据包。

在一个定位中包含多个limit_req指令

可以在单个定位(location)中包含多个limit_req指令。匹配给定的请求限制都会被使用,这意味着采用最严格的限制。例如,如果多于一个的指令使用了延迟,最终使用最长的延迟。类似地,如果某个指令使得请求被拒绝,即使其它的指令允许请求通过,最终还是被拒绝。

http {
# ...

limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s;

server {
# ...
location / {
limit_req zone=req_zone burst=10 nodelay;
limit_req zone=req_zone_wl burst=20 nodelay;
# ...
}
}
}

配置相关的特性

日志(Logging)

默认,NGNIX记录由于限流导致的延迟或丢弃的请求的日志,如下面的例子:

2015/06/13 04:20:00 [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.1.2, server: nginx.com, request: "GET / HTTP/1.0", host: "nginx.com"

该日志记录包含的字段:

  • limiting requests — 日志条目记录了某个限流的标志
  • excess — 超过这个请求代表的配置的速率的每毫秒请求数目
  • zone — 定义了启用了限流的区域
  • client — 产生请求的客户端IP地址
  • server — 服务器的IP地址或主机名
  • request — 客户端产生的实际的HTTP请求
  • host — HTTP头部主机名的值

默认,NGINX日志在error级别拒绝请求,如上面例子中的[error]所示。(它在低一个级别上记录延迟的请求,因此默认是info。)用limit_req_log_level指令来改变日志级别。下面我们设置在warn级别上记录被拒绝的请求的日志:

location /login/ {
limit_req zone=mylimit burst=20 nodelay;
limit_req_log_level warn;

proxy_pass http://my_upstream;
}

发送给客户端的错误码

默认,当某个客户端超过它的限流,NGINX用503(Service Temporarily Unavailable)状态码来响应。使用limit_req_status指令设置一个不同的状态码(在下面的例子是444):

location /login/ {
limit_req zone=mylimit burst=20 nodelay;
limit_req_status 444;
}

拒绝对特定位置的所有请求

如果你想拒绝对于某个特定URL的所有请求,而不是仅仅的限制它们,可以为这个URL配置一个location块,并且在其中包含deny all指令:

location /foo.php {
deny all;
}

原文出处

原文链接:https://www.jianshu.com/p/2cf3d9609af3

往期精彩文章


您的关注是小站的动力

欢迎大家关注交流,定期分享自动化运维、DevOps、Kubernetes、Service Mesh和Cloud Native

扫码『加群』交流技术

以上是关于NGINX 上的限流(译)的主要内容,如果未能解决你的问题,请参考以下文章

关于nginx的限流

如何利用redis来进行分布式集群系统的限流设计

nginx的限流问题

Nginx限流和黑名单配置

详解4种经典的限流算法

详解4种经典的限流算法