【转载请注明出处】:https://cloud.tencent.com/developer/article/1623156
限流并非新鲜事,在生活中亦无处不在,下面例举一二:
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流
常用的限流算法有令牌桶和和漏桶,而Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。
把请求比作是水,水来了都先放进桶里,并以限定的速度出水,当水来得过猛而出水不够快时就会导致水直接溢出,即拒绝服务。
漏斗有一个进水口 和 一个出水口,出水口以一定速率出水,并且有一个最大出水速率:
在漏斗中没有水的时候
在漏斗中有水的时候
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。
令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。
漏桶
漏桶的出水速度是恒定的,那么意味着如果瞬时大流量的话,将有大部分请求被丢弃掉(也就是所谓的溢出)。
令牌桶
生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法可以在短时间内请求拿到大量令牌,而且拿令牌的过程并不是消耗很大的事情。
Nginx官方版本限制IP的连接和并发分别有两个模块:
Nginx按请求速率限速模块使用的是漏桶算法,即能够强行保证请求的实时处理速度不会超过设置的阈值。
指令
Syntax: limit_req zone=name burst=number; Default: —
Context: http, server, location
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
limit_req zone=one burst=5 nodelay;
为服务器由于超过频次或延迟处理而拒绝处理请求的情况设置所需的日志记录级别。延迟的日志记录级别比拒绝的日志记录级别低1级;例如,“limit_req_log_level notice” 的延迟日志记录级别是info。
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,他们都能得到执行吗?
漏桶漏出请求是匀速的。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。
逻辑上叫漏桶,实现起来是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了,只要入桶就开始执行。
要么立刻执行,要么被拒绝,请求不会因为限流而增加延迟了。因为请求从桶里漏出来还是匀速的(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
这样通过控制delay参数的值,可以调整允许并发执行的请求的数量,使得请求变的均匀起来,在有些耗资源的服务上控制这个数量,还是有必要的。
这个模块用来限制单个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;
}
需要注意的是:只有当 request header 被后端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; Default: limit_conn_status 503;
Context: http, server, location
限流主要针对外部访问,内网访问相对安全,可以不做限流,通过设置白名单即可。利用 Nginx ngx_http_geo_module 和 ngx_http_map_module 两个工具模块即可搞定。
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_rate 100k;
}
这个限制是针对每个请求的,表示客户端下载前20M时不限速,后续限制100kb/s。
可以限制特定UA(比如爬虫)的访问
limit_req_zone $anti_spider zone=one:10m rate=10r/s;
limit_req zone=one burst=100 nodelay;
if ($http_user_agent ~* (YisouSpider|Scrapy)) {
set $anti_spider $http_user_agent;
}
【转载请注明出处】:https://cloud.tencent.com/developer/article/1623156
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。