Nginx限流
一、背景
限流的目的是通过对并发访问/请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页),多应用于高并发场景和安全防护场景。通过限流有效地减缓暴力密码破解攻击,也可以有效减缓 DDOS 攻击的破坏性,还可以防止上游服务器被大量并发的请求耗尽资源。
常见的限流有:限制总并发数(数据库连接池)、限制瞬时并发数(如nginx的ngx_http_limit_conn_module模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(nginx的ngx_http_limit_req_module模块),用来限制每秒的平均速率)
二、原理流程图
漏桶算法
漏桶(Leaky Bucket)算法和令牌桶(Token Bucket)算法被广泛使用于通信领域进行流量整形和速率控制。Nginx采用的是漏桶(Leaky Bucket)算法来实现速率控制。
漏桶 (Leaky Bucket) 算法思路:可以把用户请求比做水先进入到漏桶里,漏桶以一定的速度出水 (处理请求),当水流入速度过大会直接溢出 (访问频率超过接口响应速率),然后就拒绝请求。可以看出漏桶算法能强行限制数据的传输速率。
令牌桶算法
令牌桶是一个存放固定容量令牌的桶,按照固定速率r往桶里添加令牌;桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃;最后桶中可以保存的最大令牌数永远不会超过b。
传送到令牌桶的数据包需要消耗令牌。不同大小的数据包,消耗的令牌数量不一样。令牌桶这种控制机制基于令牌桶中是否存在令牌来指示什么时候可以发送流量。令牌桶中的每一个令牌都代表一个字节。如果令牌桶中存在令牌,则允许发送流量;而如果令牌桶中不存在令牌,则不允许发送流量。因此,如果突发门限被合理地配置并且令牌桶中有足够的令牌,那么流量就可以以峰值速率发送。
令牌桶算法的基本过程如下:
假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中;
假设桶最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃;
当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌,并且数据包被发送到网络;
如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外;
算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r。对于在流量限制外的数据包可以以不同的方式处理:
被丢弃;
排放在队列中以便当令牌桶中累积了足够多的令牌时再传输;
继续发送,但需要做特殊标记,网络过载的时候将这些特殊标记的包丢弃。
这两种算法的主要区别在于“漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。
三、操作配置
1.限制请求速率limit_req
场景1
指令 limit_req_zone 定义了一个大小是 10m 的名称为 one 的区域用来根据客户端地址存储状态信息,然后速率的控制是每秒 1 个请求。$binary_remote_addr是指基于访问ip来进行限流。
http { limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; server { server_name localhost; location /{ limit_req zone=one; } } }
场景2
指令 limit_req 添加 burst 参数,通过 limit_req zone=one burst=10 定义了突发处理的容量是 10。对于这些突发的请求,预期的效果是,对于前 10个请求后按照每 1r/s 的速率进行处理。然后从第 11个请求开始,所有请求都返回异常,直到有空队列出现。
http { limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; server { server_name jikui.test.com; location path1 { limit_req zone=one burst=10; } } }
其他参数
limit_req_dry_run :dry run模式。在这种模式下,只会把请求的状态信息保存在共享内存中,限速功能不会生效。
limit_req_log_level:设置与限速功能相关的 log 级别。比如设置为 limit_req_log_level notice 则限速相关的 log 会按照 notice 的级别进行记录。
limit_req_status:设置 NGINX 拒绝连接请求时发送给客户端的 HTTP 代码,默认是 503。
2.限制连接数limit_conn
场景1:每个ip只允许1个连接
limit_conn_zone $binary_remote_addr zone=addr:10m; server { location / { limit_conn addr 1; }
场景2:限制每个客户端 IP 与服务器的连接数,同时限制与虚拟服务器的连接总数
limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn_zone $server_name zone=perserver:10m; server { ... limit_conn perip 10; limit_conn perserver 100; }
微服务场景限流:https://sentinelguard.io/zh-cn/docs/introduction.html