【转载请注明出处】:juejin.im/post/5eacf0…html
限流并不是新鲜事,在生活中亦无处不在,下面例举一二:node
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流nginx
经常使用的限流算法有令牌桶和和漏桶,而Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。算法
把请求比做是水,水来了都先放进桶里,并以限定的速度出水,当水来得过猛而出水不够快时就会致使水直接溢出,即拒绝服务。 后端
在漏斗中没有水的时候缓存
在漏斗中有水的时候安全
对于不少应用场景来讲,除了要求可以限制数据的平均传输速率外,还要求容许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。 bash
令牌桶算法的原理是系统以恒定的速率产生令牌,而后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,须要从令牌桶中取出一个令牌,若是此时令牌桶中没有令牌,那么则拒绝该请求。服务器
漏桶 漏桶的出水速度是恒定的,那么意味着若是瞬时大流量的话,将有大部分请求被丢弃掉(也就是所谓的溢出)。并发
令牌桶 生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法能够在短期内请求拿到大量令牌,并且拿令牌的过程并非消耗很大的事情。
Nginx官方版本限制IP的链接和并发分别有两个模块:
Nginx按请求速率限速模块使用的是漏桶算法,即可以强行保证请求的实时处理速度不会超过设置的阈值。 指令
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;
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;
}
复制代码
limit_conn perip 10 做用的key 是 $binary_remote_addr,表示限制单个IP同时最多能持有10个链接。
limit_conn perserver 100 做用的key是 $server_name,表示虚拟主机(server) 同时能处理并发链接的总数。
须要注意的是:只有当 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;
}
复制代码
【转载请注明出处】:juejin.im/post/5eacf0…