nginx 流控使用的项目实践
作者:liulanba
正常限制访问频率(正常流量)
流量限制”配置两个主要的指令,limit_req_zone和limit_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_zone指令定义了流量限制相关的参数,而limit_req指令在出现的上下文中启用流量限制(示例中,对于”/login/”的所有请求)。limit_req_zone指令通常在 HTTP 块中定义,使其可在多个上下文中使用,它需要以下三个参数:
1)Key - 定义应用限制的请求特性。示例中的 Nginx 变量remote_addr,占用更少的空间)
2)Zone - 定义用于存储每个 IP 地址状态以及被限制请求 URL 访问频率的共享内存区域。保存在内存共享区域的信息,意味着可以在 Nginx 的 worker 进程之间共享。定义分为两个部分:通过zone=keyword标识区域的名字,以及冒号后面跟区域大小。16000 个 IP 地址的状态信息,大约需要 1MB,所以示例中区域可以存储 160000 个 IP 地址。
3)Rate - 定义最大请求速率。在示例中,速率不能超过每秒 10 个请求。Nginx 实际上以毫秒的粒度来跟踪请求,所以速率限制相当于每 100 毫秒 1 个请求。因为不允许”突发情况”,这意味着在前一个请求 100 毫秒内到达的请求将被拒绝。
当 Nginx 需要添加新条目时存储空间不足,将会删除旧条目。如果释放的空间仍不够容纳新记录,Nginx 将会返回 503状态码(Service Temporarily Unavailable)。另外,为了防止内存被耗尽,Nginx 每次创建新条目时,最多删除两条 60 秒内未使用的条目。
limit_req_zone指令设置流量限制和共享内存区域的参数,但实际上并不限制请求速率。所以需要通过添加limit_req指令,将流量限制应用在特定的 location 或者 server 块。在上面示例中,我们对/login/请求进行流量限制。
现在每个 IP 地址被限制为每秒只能请求 10 次 /login/,更准确地说,在前一个请求的 100 毫秒内不能请求该 URL。
突发限制访问频率(突发流量)
如果我们在 100 毫秒内接收到 2 个请求,怎么办?对于第二个请求,Nginx 将给客户端返回状态码 503。这可能并不是我们想要的结果,因为应用本质上趋向于突发性。相反地,我们希望缓冲任何超额的请求,然后及时地处理它们。我们更新下配置,在limit_req中使用 burst 参数:
location /login/ { limit_req zone=mylimit burst=20; proxy_pass http://my_upstream; }
burst 参数定义了超出 zone 指定速率的情况下(示例中的 mylimit 区域,速率限制在每秒 10 个请求,或每 100 毫秒一个请求),客户端还能发起多少请求。上一个请求 100 毫秒内到达的请求将会被放入队列,我们将队列大小设置为 20。
这意味着,如果从一个给定 IP 地址发送 21 个请求,Nginx 会立即将第一个请求发送到上游服务器群,然后将余下 20 个请求放在队列中。然后每 100 毫秒转发一个排队的请求,只有当传入请求使队列中排队的请求数超过 20 时,Nginx 才会向客户端返回 503。
无延迟的排队
配置 burst 参数将会使通讯更流畅,但是可能会不太实用,因为该配置会使站点看起来很慢。在上面的示例中,队列中的第 20 个包需要等待 2 秒才能被转发,此时返回给客户端的响应可能不再有用。要解决这个情况,可以在 burst 参数后添加 nodelay 参数:
location /login/ { limit_req zone=mylimit burst=20 nodelay; proxy_pass http://my_upstream; }
使用 nodelay 参数,Nginx 仍将根据 burst 参数分配队列中的位置,并应用已配置的速率限制,而不是清理队列中等待转发的请求。相反地,当一个请求到达“太早”时,只要在队列中能分配位置,Nginx 将立即转发这个请求。将队列中的该位置标记为”taken”(占据),并且不会被释放以供另一个请求使用,直到一段时间后才会被释放(在这个示例中是,100 毫秒后)。
假设如前所述,队列中有 20 个空位,从给定的 IP 地址发出的 21 个请求同时到达。Ngin x会立即转发这个 21 个请求,并且标记队列中占据的 20 个位置,然后每 100 毫秒释放一个位置。如果是25个请求同时到达,Nginx 将会立即转发其中的 21 个请求,标记队列中占据的 20 个位置,并且返回 503 状态码来拒绝剩下的 4 个请求。
现在假设,第一组请求被转发后 101 毫秒,另 20 个请求同时到达。队列中只会有一个位置被释放,所以 Nginx 转发一个请求并返回503状态码来拒绝其他 19 个请求。如果在 20 个新请求到达之前已经过去了 501 毫秒,5 个位置被释放,所以 Nginx 立即转发 5 个请求并拒绝另外 15 个。
效果相当于每秒 10 个请求的“流量限制”。如果希望不限制两个请求间允许间隔的情况下实施“流量限制”,nodelay 参数是很实用的。
注意:对于大部分部署,我们建议使用 burst 和 nodelay 参数来配置limit_req指令。
限制并发连接数
Nginx 的ngx_http_limit_conn_module模块提供了对资源连接数进行限制的功能,使用 limit_conn_zone 和 limit_conn 两个指令就可以了。
properties复制代码limit_conn perip 20 : 对应的key是 $binary_remote_addr,表示限制单个IP同时最多能持有20个连接。
limit_conn perserver 100 : 对应的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。注意,只有当 request header 被后端server处理后,这个连接才进行计数。
修改nginx.conf配置,如下:
#速率限流配置 limit_req_zone $binary_remote_addr zone=ipRateLimit:10m rate=2r/s; #并发量限流-单个IP控制 limit_conn_zone $binary_remote_addr zone=perip:10m; #并发量限流-整个服务控制 limit_conn_zone $server_name zone=perserver:10m; server { listen 80; server_name localhost; location ~ .*\.(woff|ico|css|js|gif|jpg|jpeg|png)$ { root /usr/local/openrestyDir/pages/; } location /msitems/ { #限流配置 #limit_req zone=ipRateLimit burst=5 nodelay; #并发量限流 limit_conn perip 10; limit_conn perserver 100; root /usr/local/openrestyDir/pages/; } }
上面配置了单个IP同时并发连接数最多只能10个连接,并且设置了整个虚拟服务器同时最大并发数最多只能100个链接。当然,只有当请求的header被服务器处理后,虚拟服务器的连接数才会计数。刚才有提到过Nginx是基于漏桶算法原理实现的,实际上限流一般都是基于漏桶算法和令牌桶算法实现的。接下来我们来看看两个算法的介绍:
漏桶算法
漏桶算法是网络世界中流量整形或速率限制时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。也就是我们刚才所讲的情况。漏桶算法提供的机制实际上就是刚才的案例:突发流量会进入到一个漏桶,漏桶会按照我们定义的速率依次处理请求,如果水流过大也就是突发流量过大就会直接溢出,则多余的请求会被拒绝。所以漏桶算法能控制数据的传输速率。
令牌桶算法
令牌桶算法是网络流量整形和速率限制中最常使用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。令牌桶算法的机制如下:存在一个大小固定的令牌桶,会以恒定的速率源源不断产生令牌。如果令牌消耗速率小于生产令牌的速度,令牌就会一直产生直至装满整个令牌桶。
黑白名单
有时候会有一些恶意IP攻击服务器,会基于程序频繁发起请求对服务器造成巨大压力,我们此时可以使用Nginx的黑名单功能实现黑名单过滤操作。我们首先需要配置黑名单IP,黑名单IP我们可以记录到一个配置文件中,比如配置到blockip.conf文件中:
配置固定IP为黑名单:
deny 192.168.100.1;
在nginx.conf中引入blockip.conf,可以放到http, server, location语句块,配置如下:
#黑名单 include blockip.conf;
此时在192.168.100.1的IP上访问服务器,会报403错误
屏蔽ip的配置文件既可以屏蔽单个ip,也可以屏蔽ip段,或者只允许某个ip或者某个ip段访问。
# 屏蔽单个ip访问 deny IP; # 允许单个ip访问 allow IP; # 屏蔽所有ip访问 deny all; # 允许所有ip访问 allow all; #屏蔽整个段即从123.0.0.1到123.255.255.254访问的命令 deny 123.0.0.0/8 #屏蔽IP段即从123.45.0.1到123.45.255.254访问的命令 deny 124.45.0.0/16 #屏蔽IP段即从123.45.6.1到123.45.6.254访问的命令 deny 123.45.6.0/24
如果你想实现这样的应用,除了几个IP外,其他全部拒绝,那需要你在blockip.conf中这样写:
allow 192.168.100.1; allow 192.168.100.2; deny all;
但是这种手动配置的方式可能太过繁琐,我们也可以配置动态黑白名单。
配置动态黑白名单,我们可以采用Lua+Redis实现,将黑名单存入到Redis缓存,每次执行请求时,通过lua脚本先获取用户IP,匹配IP是否属于黑名单,如果是,则不让请求,如果不是,则放行。
参考链接:
https://juejin.cn/post/7120044094239277070
https://cloud.tencent.com/developer/article/1912141
到此这篇关于nginx 流控使用的项目实践的文章就介绍到这了,更多相关nginx 流控内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!