1.什么是流量控制
流量限制(rate-limiting),是Nginx中一个非常实用,却经常被错误理解和错误配置的功能。我们可以用来限制用户在给定时间内HTTP请求的数量。请求,可以是一个简单网站首页的GET请求,也可以是登录表单的 POST 请求。流量限制可以用作安全目的,比如可以减慢暴力密码破解的速率。通过将传入请求的速率限制为真实用户的典型值,并标识目标URL地址(通过日志),还可以用来抵御 DDOS 攻击。更常见的情况,该功能被用来保护上游应用服务器不被同时太多用户请求所压垮。
2.Nginx如何限流
Nginx的”流量限制”使用漏桶算法(leaky bucket algorithm),该算法在通讯和分组交换计算机网络中广泛使用,用以处理带宽有限时的突发情况。就好比,一个桶口在倒水,桶底在漏水的水桶。如果桶口倒水的速率大于桶底的漏水速率,桶里面的水将会溢出;同样,在请求处理方面,水代表来自客户端的请求,水桶代表根据”先进先出调度算法”(FIFO)等待被处理的请求队列,桶底漏出的水代表离开缓冲区被服务器处理的请求,桶口溢出的水代表被丢弃和不被处理的请求。
3.限流实战
1.普通Nginx限流
“流量限制”配置两个主要的指令,limit_req_zone
和limit_req
,limit_req_zone
指令定义了流量限制相关的参数,而limit_req
指令在出现的上下文中启用流量限制。
[root@testhost ~]# vim /etc/nginx/conf.d/default.conf
[root@testhost ~]# nginx -s reload
定义了每秒只能处理10个请求,也就是100ms处理1个请求,如果100ms内收到两个请求,Nginx将给客户端返回状态码503。
limit_req_zone
指令通常在HTTP块中定义,使其可在多个上下文中使用,它需要以下三个参数:
-
Key - 定义应用限制的请求特性。示例中的 Nginx 变量
$binary_remote_addr
,保存客户端IP地址的二进制形式。这意味着,我们可以将每个不同的IP地址限制到,通过第三个参数设置的请求速率。(使用该变量是因为比字符串形式的客户端IP地址$remote_addr
,占用更少的空间) -
Zone - 定义用于存储每个IP地址状态以及被限制请求URL访问频率的共享内存区域。保存在内存共享区域的信息,意味着可以在Nginx的worker进程之间共享。定义分为两个部分:通过
zone=keyword
标识区域的名字,以及冒号后面跟区域大小。16000个IP地址的状态信息,大约需要1MB,所以示例中区域可以存储160000个IP地址。 -
Rate - 定义最大请求速率。在示例中,速率不能超过每秒10个请求。Nginx实际上以毫秒的粒度来跟踪请求,所以速率限制相当于每100毫秒1个请求。因为不允许”突发情况”(见下一章节),这意味着在前一个请求100毫秒内到达的请求将被拒绝。
当Nginx需要添加新条目时存储空间不足,将会删除旧条目。如果释放的空间仍不够容纳新记录,Nginx将会返回 503状态码(Service Temporarily Unavailable)。另外,为了防止内存被耗尽,Nginx每次创建新条目时,最多删除两条60秒内未使用的条目。
2.突发处理
上述我们在100毫秒内接收到2个请求,怎么办?对于第二个请求,Nginx将给客户端返回状态码503。这可能并不是我们想要的结果,因为应用本质上趋向于突发性。相反地,我们希望缓冲任何超额的请求,然后及时地处理它们。我们更新下配置,在limit_req
中使用burst
参数:
burst
参数定义了超出zone指定速率的情况下(示例中的limit
区域,速率限制在每秒10个请求,或每100毫秒一个请求),客户端还能发起多少请求。上一个请求100毫秒内到达的请求将会被放入队列,我们将队列大小设置为20。这里我们用脚本访问服务网站:可以发现设置burst每一个请求都成功访问,但是所耗费的时间太长。
3.无延迟的排队
在上述配置burst队列参数会使通讯更流畅,但是所耗的时间比较长不太理想;这样会让站点看起来很慢,在上面的示例中,队列中的第20个包需要等待2秒才能被转发,此时返回给客户端的响应可能不再有用。要解决这个情况,可以在burst
参数后添加nodelay
参数:
假设,第一组请求被转发后101毫秒,另20个请求同时到达。队列中只会有一个位置被释放,所以Nginx转发一个请求并返回503状态码来拒绝其他19个请求。如果在20个新请求到达之前已经过去了501毫秒,5个位置被释放,所以Nginx立即转发5个请求并拒绝另外15个。
我们发出22个请求可以看到前21个别回应,第22个请求别拒接原因是第1个请求直接被处理,接下来20个在队列中,第22个请求溢出服务器返回503.
4.白名单
将192.168.134.1加入白名单可以取消对它的限流:
[root@testhost ~]# systemctl restart nginx
这个例子同时使用了
geo
和map
指令。geo
块将给在白名单中的IP地址对应的$limit
变量分配一个值ok,给其它不在白名单中的分配一个值no。然后我们使用一个映射将这些值转为key,如下:
如果
$limit
变量的值是ok,$limit_key
变量将被赋值为空字符串如果
$limit
变量的值是no,$limit_key
变量将被赋值为客户端二进制形式的IP地址两个指令配合使用,白名单内IP地址的
$key
变量被赋值为空字符串,不在白名单内的被赋值为客户端的IP地址。当limit_req_zone
后的第一个参数是空字符串时,不会应用“流量限制”,所以白名单内的IP地址192.168.134.1不会被限制。其它所有IP地址都会被限制到每秒10个请求。
limit_req
指令将限制应用到/的location块,允许在配置的限制上最多超过20个数据包的突发,并且不会延迟转发。
将192.168.134.1加入白名单用脚本访问:
当192.168.134.1不在白名单内队列以外的溢出返回503:
5.location 包含多limit_req
指令
我们可以在一个location块中配置多个limit_req
指令。符合给定请求的所有限制都被应用时,意味着将采用最严格的那个限制。例如,多个指令都制定了延迟,将采用最长的那个延迟。同样,请求受部分指令影响被拒绝,即使其他指令允许通过也无济于事。
这里依旧将192.168.134.1放在白名单内但是又新加了一条规则:
用192.168.134.1访问服务发现会限流:
这说明如果同时有几个限制那么就会采用最严格的那个限制!!!!!