Nginx|Nginx 限流模块
【转载请注明出处】:https://www.jianshu.com/p/790373b4903b
生活中的 “限流”?
限流并非新鲜事,在生活中亦无处不在,下面例举一二:
- 博物馆:限制每天参观总人数以保护文物
- 高铁安检:有若干安检口,旅客依次排队,工作人员根据安检快慢决定是否放人进去。遇到节假日,可以增加安检口来提高处理能力(横向拓展),同时增加排队等待区长度(缓存待处理任务)。
- 办理银行业务:所有人先领号,各窗口叫号处理。每个窗口处理速度根据客户具体业务而定,所有人排队等待叫号即可。若快下班时,告知客户明日再来(拒绝流量)。
- 水坝泄洪:水坝可以通过闸门控制泄洪速度(控制处理速度)。
- 缓存:缓存的目的是提升系统访问速度和增大系统处理容量
- 降级:降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行
- 限流:限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理
常用的限流算法有令牌桶和和漏桶,而Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。
漏桶算法 把请求比作是水,水来了都先放进桶里,并以限定的速度出水,当水来得过猛而出水不够快时就会导致水直接溢出,即拒绝服务。
文章图片
image.png
漏斗有一个进水口 和 一个出水口,出水口以一定速率出水,并且有一个最大出水速率:
在漏斗中没有水的时候
- 如果进水速率小于等于最大出水速率,那么,出水速率等于进水速率,此时,不会积水
- 如果进水速率大于最大出水速率,那么,漏斗以最大速率出水,此时,多余的水会积在漏斗中
- 出水口以最大速率出水
- 如果漏斗未满,且有进水的话,那么这些水会积在漏斗中
- 如果漏斗已满,且有进水的话,那么这些水会溢出到漏斗之外
文章图片
image.png 令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。
令牌桶算法VS漏桶算法 漏桶
漏桶的出水速度是恒定的,那么意味着如果瞬时大流量的话,将有大部分请求被丢弃掉(也就是所谓的溢出)。
令牌桶
生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,而且拿令牌的过程并不是消耗很大的事情。
Nginx限流
Nginx官方版本限制IP的连接和并发分别有两个模块:
- limit_req_zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 "leaky bucket"。
- limit_req_conn 用来限制同一时间连接数,即并发限制。
指令
Syntax: limit_req zone=name [burst=number] [nodelay | delay=number];正常流量
Default: —
Context: http, server, location
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
- key :定义限流对象,binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
- zone:定义共享内存区来存储访问信息, one:10m 表示一个大小为10M,名字为one的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。
- rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求。如果限制的频率低于1r/s,则可以使用r/m,如30r/m。
limit_req zone=one burst=5 nodelay;
- zone=one 设置使用哪个配置区域来做限制,与上面limit_req_zone 里的name对应。
- burst=5,burst爆发的意思,这个配置的意思是设置一个大小为5的缓冲区当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内。
- nodelay,如果设置,超过访问频次而且缓冲区也满了的时候就会直接返回503,如果没有设置,则所有请求会等待排队。
Syntax: limit_req_log_level info | notice | warn | error;拒绝响应状态码
Default: limit_req_log_level error;
Context: http, server, location
Syntax: limit_req_status code;案例
Default: limit_req_status 503;
Context: http, server, location
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /search/ {
limit_req zone=one;
}
}
限流速度为每秒10次请求,如果有10次请求同时到达一个空闲的nginx,他们都能得到执行吗?
文章图片
image.png 漏桶漏出请求是匀速的。10r/s是怎样匀速的呢?每100ms漏出一个请求。在这样的配置下,桶是空的,所有不能实时漏出的请求,都会被拒绝掉。所以如果10次请求同时到达,那么只有一个请求能够得到执行,其它的,都会被拒绝。
这不太友好,大部分业务场景下我们希望这10个请求都能得到执行,添加突发流量处理机制。
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /search/ {
limit_req zone=one burst=12;
}
}
burst=12 漏桶的大小设置为12。
文章图片
image.png 逻辑上叫漏桶,实现起来是FIFO队列,把得不到执行的请求暂时缓存起来。这样漏出的速度仍然是100ms一个请求,但就并发而言,暂时得不到执行的请求,可以先缓存起来。只有当队列满了的时候,才会拒绝接受新请求。这样漏桶在限流的同时,也起到了削峰填谷的作用。
在这样的配置下,如果有10次请求同时到达,它们会依次执行,每100ms执行1个。虽然得到执行了,但因为排队执行,延迟大大增加,在很多场景下仍然是不能接受的。继续修改配置,解决Delay太久导致延迟增加的问题。
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /search/ {
limit_req zone=one burst=12 nodelay;
}
}
nodelay 把开始执行请求的时间提前,以前是delay到从桶里漏出来才执行,现在不delay了,只要入桶就开始执行。
文章图片
image.png 要么立刻执行,要么被拒绝,请求不会因为限流而增加延迟了。因为请求从桶里漏出来还是匀速的(100ms释放1个),桶的空间又是固定的,最终平均下来,还是每秒执行了10次请求,限流的目的还是达到了。
但是请注意,虽然设置burst和nodelay能够降低突发请求的处理时间,但是长期来看并不会提高吞吐量的上限,长期吞吐量的上限是由rate决定的,因为nodelay只能保证burst的请求被立即处理,但Nginx会限制队列元素释放的速度,就像是限制了令牌桶中令牌产生的速度。
但这样也有缺点,限流是限了,但是限得不那么匀速。以上面的配置举例,如果有12个请求同时到达,那么这12个请求都能够立刻执行,然后后面的请求只能匀速进桶,100ms执行1个。如果有一段时间没有请求,桶空了,那么又可能出现并发的12个请求一起执行。
大部分情况下,这种限流不匀速,不算是大问题。不过nginx也提供了一个参数控制并发执行也就是nodelay的请求的数量。
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /search/ {
limit_req zone=one burst=12 delay=4;
}
}
delay=4 从桶内第5个请求开始delay
文章图片
image.png 这样通过控制delay参数的值,可以调整允许并发执行的请求的数量,使得请求变的均匀起来,在有些耗资源的服务上控制这个数量,还是有必要的。
ngx_http_limit_conn_module 模块 这个模块用来限制单个IP的请求数。并非所有的连接都被计数。只有在服务器处理了请求并且已经读取了整个请求头时,连接才被计数。
Syntax: limit_conn zone number;如:
Default: —
Context: http, server, location
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;
}
- limit_conn perip 10 作用的key 是 $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。
- limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。
日志级别
Syntax: limit_conn_log_level info | notice | warn | error;拒绝响应状态码
Default: limit_conn_log_level error;
Context: http, server, location
Syntax: limit_conn_status code;设置白名单 限流主要针对外部访问,内网访问相对安全,可以不做限流,通过设置白名单即可。利用 Nginx ngx_http_geo_module 和 ngx_http_map_module 两个工具模块即可搞定。
Default: limit_conn_status 503;
Context: http, server, location
geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/24 0;
172.20.0.35 0;
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=myRateLimit:10m rate=10r/s;
geo 对于白名单(子网或IP都可以) 将返回0,其他IP将返回1。
map 将
$limit
转换为 $limit_key
,如果是 $limit
是0(白名单),则返回空字符串;如果是1,则返回客户端实际IP。limit_req_zone 限流的key不再使用
$binary_remote_addr
,而是 $limit_key
来动态获取值。如果是白名单,limit_req_zone 的限流key则为空字符串,将不会限流;若不是白名单,将会对客户端真实IP进行限流。限制数据传输速度 除限流外,ngx_http_core_module 还提供了限制数据传输速度的能力(即常说的下载速度)。
例如:
location /flv/ {
flv;
limit_rate_after 20m;
limit_rate100k;
}
这个限制是针对每个请求的,表示客户端下载前20M时不限速,后续限制100kb/s。
限制特定UA 可以限制特定UA(比如爬虫)的访问
limit_req_zone$anti_spiderzone=one:10mrate=10r/s;
limit_req zone=one burst=100 nodelay;
if ($http_user_agent ~* (YisouSpider|Scrapy)) {
set $anti_spider $http_user_agent;
}
【Nginx|Nginx 限流模块】【转载请注明出处】:https://www.jianshu.com/p/790373b4903b
文章图片
推荐阅读
- Guava|Guava RateLimiter与限流算法
- SpringBoot调用公共模块的自定义注解失效的解决
- Node.js中readline模块实现终端输入
- python自定义封装带颜色的logging模块
- 解决SpringBoot引用别的模块无法注入的问题
- Python(pathlib模块)
- Apache多路复用模块(MPMs)介绍
- 监控nginx
- 依赖注入模块
- Linux|Linux 服务器nginx相关命令