Nginx 限流模块

【转载请注明出处】:juejin.im/post/5eacf0…html

生活中的 “限流”?

限流并不是新鲜事,在生活中亦无处不在,下面例举一二:node

  • 博物馆:限制天天参观总人数以保护文物
  • 高铁安检:有若干安检口,旅客依次排队,工做人员根据安检快慢决定是否放人进去。遇到节假日,能够增长安检口来提升处理能力(横向拓展),同时增长排队等待区长度(缓存待处理任务)。
  • 办理银行业务:全部人先领号,各窗口叫号处理。每一个窗口处理速度根据客户具体业务而定,全部人排队等待叫号便可。若快下班时,告知客户明日再来(拒绝流量)。
  • 水坝泄洪:水坝能够经过闸门控制泄洪速度(控制处理速度)。

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流nginx

  • 缓存:缓存的目的是提高系统访问速度和增大系统处理容量
  • 降级:降级是当服务器压力剧增的状况下,根据当前业务状况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行
  • 限流:限流的目的是经过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则能够拒绝服务、排队或等待、降级等处理

两大限流算法

经常使用的限流算法有令牌桶和和漏桶,而Google开源项目Guava中的RateLimiter使用的就是令牌桶控制算法。算法

漏桶算法

把请求比做是水,水来了都先放进桶里,并以限定的速度出水,当水来得过猛而出水不够快时就会致使水直接溢出,即拒绝服务。 后端

image.png
漏斗有一个进水口 和 一个出水口,出水口以必定速率出水,而且有一个最大出水速率:

在漏斗中没有水的时候缓存

  • 若是进水速率小于等于最大出水速率,那么,出水速率等于进水速率,此时,不会积水
  • 若是进水速率大于最大出水速率,那么,漏斗以最大速率出水,此时,多余的水会积在漏斗中

在漏斗中有水的时候安全

  • 出水口以最大速率出水
  • 若是漏斗未满,且有进水的话,那么这些水会积在漏斗中
  • 若是漏斗已满,且有进水的话,那么这些水会溢出到漏斗以外
令牌桶算法

对于不少应用场景来讲,除了要求可以限制数据的平均传输速率外,还要求容许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。 bash

image.png

令牌桶算法的原理是系统以恒定的速率产生令牌,而后把令牌放到令牌桶中,令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌,那么多余的令牌会被丢弃;当想要处理一个请求的时候,须要从令牌桶中取出一个令牌,若是此时令牌桶中没有令牌,那么则拒绝该请求。服务器

令牌桶算法VS漏桶算法

漏桶 漏桶的出水速度是恒定的,那么意味着若是瞬时大流量的话,将有大部分请求被丢弃掉(也就是所谓的溢出)。并发

令牌桶 生成令牌的速度是恒定的,而请求去拿令牌是没有速度限制的。这意味,面对瞬时大流量,该算法能够在短期内请求拿到大量令牌,并且拿令牌的过程并非消耗很大的事情。

Nginx限流

Nginx官方版本限制IP的链接和并发分别有两个模块:

  • limit_req_zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 "leaky bucket"。
  • limit_req_conn 用来限制同一时间链接数,即并发限制。

ngx_http_limit_req_module 模块

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;

  • 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,若是没有设置,则全部请求会等待排队。
日志级别

为服务器因为超过频次或延迟处理而拒绝处理请求的状况设置所需的日志记录级别。延迟的日志记录级别比拒绝的日志记录级别低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,他们都能获得执行吗?

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) 同时能处理并发链接的总数。

须要注意的是:只有当 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

能够限制特定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…

相关文章
相关标签/搜索