微信搜索superit|邀请体验:大数据, 数据管理、OLAP分析与可视化平台 | 赞助作者:赞助作者

Nginx下limit_req模块burst参数超详细解析

nginx aide_941 7℃

在学习Nginx的时候遇到了这个问题,百度到了很多博客,大多都讲得不清不楚,在看到了

http://www.wangjingfeng.com/730.html

http://cjhust.blog.163.com/blog/static/17582715720111017114121678/

这两篇博客之后终于疑窦瞬开,在综合了之前看到的博客再加上测试案例之后整理成文。

引言:

漏斗算法(Leaky Bucket),该算法有两种处理方式Traffic Shaping和Traffic Policing

在桶满水之后,常见的两种处理方式为:

  1. 暂时拦截住上方水的向下流动,等待桶中的一部分水漏走后,再放行上方水。
  2. 溢出的上方水直接抛弃。

 

将水看作网络通信中数据包的抽象,则方式1起到的效果称为Traffic Shaping,方式2起到的效果称为Traffic Policing

由此可见,Traffic Shaping的核心理念是"等待",Traffic Policing的核心理念是"丢弃"。它们是两种常见的流速控制方法

 

源码:

limit_req模块的源码在src/http/modules/ngx_http_limit_req_module.c,关于burst的核心代码在这。//src/http/modules/ngx_http_limit_req_module.c:396

  1. ms = (ngx_msec_int_t) (now – lr->last);
  2. excess = lr->excess – ctx->rate * ngx_abs(ms) / 1000 + 1000;
  3. if (excess < 0) {
  4.     excess = 0;
  5. }
  6. *ep = excess;
  7. if ((ngx_uint_t) excess > limit->burst) {
  8.    return NGX_BUSY;
  9. }
  10. if (account) {
  11.     lr->excess = excess;
  12.     lr->last = now;
  13.    return NGX_OK;
  14. }

excess初始值是0,假设现在ctx->rate是2000(即2 request/s),这次请求距离上次请求是400ms。

那么excess = 0 – 2000 * 400 / 1000 + 1000 = 200。如果limit->burst是0,那么200 > 0,会返回NGX_BUSY即是503了。

假如burst是1,limit->burst即是1000,那么如果请求是每隔400ms来一个,共需5个才会填满limit->burst(每个请求将会增加200 excess),到第6个才会返回503。

推导出公式,假设设置频率是r request/s,每次请求距离上次请求t ms,设置burst为b,

那么返回503的临界请求个数x是

  1. \begin{equation}
  2. x = floor(b * \frac {( 1000 / r )} {( 1000 / r – t )})
  3. \end{equation}

虽然知道了源码但是还是没有一个非常直观的感觉,所以不如实际操作一下:

 

实际操作:

nginx中该模块的使用配置示例l

  1. imit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
  2. server {
  3.     location  /search/ {
  4.         limit_req zone=one burst=5 nodelay;
  5.     }

第一段配置参数:

  • $binary_remote_addr :表示通过remote_addr这个标识来做限制,“binary_”的目的是缩写内存占用量,是限制同一客户端ip地址
  • zone=one:10m:表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息
  • rate=1r/s:表示允许相同标识的客户端的访问频次,这里限制的是每秒1次,即每秒只处理一个请求,还可以有比如30r/m的,即限制每2秒访问一次,即每2秒才处理一个请求

 

第二段配置参数:

  • zone=one :设置使用哪个配置区域来做限制,与上面limit_req_zone 里的name对应
  • burst=5:重点说明一下这个配置,burst爆发的意思,这个配置的意思是设置一个大小为5的缓冲区当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内等待,但是这个等待区里的位置只有5个,超过的请求会直接报503的错误然后返回。
  • nodelay:
    • 如果设置,会在瞬时提供处理(burst + rate)个请求的能力,请求超过(burst + rate)的时候就会直接返回503,永远不存在请求需要等待的情况。(这里的rate的单位是:r/s)
    • 如果没有设置,则所有请求会依次等待排队

 

这里的burst参数主要采用了令牌桶算法。令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据数目,并允许突发数据的发送。

令牌桶这种控制机制基于令牌桶中是否存在令牌来指示什么时候可以发送流量。令牌桶中的每一个令牌都代表一个字节。如果令牌桶中存在令牌,则允许发送流量;如果令牌桶中不存在令牌,则不允许发送流量。因此,如果突发门限被合理地配置并且令牌桶中有足够的令牌,那么流量就可以以峰值速率发送。令牌桶算法的基本过程如下:

 

  1. 假如用户配置的平均发送速率为10r/s,则每隔0.1秒一个令牌被加入到桶中;
  2. 假设桶最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃;
  3. 当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌,并且数据包被发送到网络;
  4. 如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外;
  5. 算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r。对于在流量限制外的数据包可以以不同的方式处理:
    • 它们可以被丢弃;
    • 它们可以排放在队列中以便当令牌桶中累积了足够多的令牌时再传输;
    • 它们可以继续发送,但需要做特殊标记,网络过载的时候将这些特殊标记的包丢弃。

 

注意:令牌桶算法不能与另外一种常见算法“漏斗算法(Leaky Bucket)”相混淆。这两种算法的主要区别在于“漏斗算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。(这部分是网上很多博客都错误的地方)

 

例子演示:

首先我们配置了limt_req_zone,rate=10r/m,即每六秒才处理一次请求,如下:

1.首先测试不加burst不加nodelay的情况:

  • 查看当前的tcp连接数
    • netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’
    • 结果如下:

  • 使用ab测试工具,发起10个并发请求:
    • ab -n 10 -c 10 url
    • ab压测工具瞬间返回了结果
    • 可以看到一共10个请求,9个请求都失败了。且0.09秒就完成了压测
  • 接着查看当前的tcp连接数: 可以观察到此时服务端的TIME_WAIT 为10,这意味着是服务端主动要求断开了所有TCP连接
  • 接着再查看 /var/log/nginx/access.log,印证了只有一个请求成功了,其它就是都直接返回了503,即服务器拒绝了请求。

 

2.只加burst不加nodelay的情况:

  • 查看当前的tcp连接数
    • netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’
    • 结果如下:

  • 使用ab测试工具,发起10个并发请求:
    • ab -n 10 -c 10 url
    • 可以看到测试经过30 s 才结束
    • 压测中一共10个请求,有4个请求失败了,直接返回了503
  • 查看当前的tcp连接数

    • 上图是ab测试第一秒时的截图,TIME_WAIT=5 表示有服务器端主动断开了5个TCP连接,即5个请求被瞬时拒绝,同时ESTABLISHED的数量由23增加到28,即建立了5个TCP连接
    • 上图是ab测试过程中的截图,TIME_WAIT=7 表示有服务器端主动断开了7个TCP连接,增加的2个TIME_WAIT是因为有2个在缓存队列的请求被处理完毕了,所以断开了连接。
  • 接着查看 /var/log/nginx/access.log日志
    • 可以观察到在39分35秒,即压测第1秒时,成功处理了1个请求,另外有4个请求瞬间返回了503,剩下的5个请求每隔6s处理一次。
    • 这是因为设置了burst=5,在服务器接收到10个并发请求后,先处理1个请求,同时将5个请求放入burst缓冲队列中,等待处理。而超过(burst+1)数量的请求就被直接抛弃了,即直接抛弃了4个请求。
  • 查看 /var/log/nginx/error.log日志
    • 发现有5个delaying request,4个limiting request

 

3.加burst加nodelay的情况:

  • 查看当前的tcp连接数
    • netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’
    • 结果如下:

  • 使用ab测试工具,发起10个并发请求:
    • ab -n 10 -c 10 url
    • 可以看到压测在0.1 s 内完成了,这也是添加nodelay参数的意义
    • 压测中一共10个请求,有4个请求失败了,直接返回了503
  • 查看当前的tcp连接数:所有的请求都在1s内处理完成了
  • 接着查看 /var/log/nginx/access.log日志
    • 可以发现在1s内,服务器端处理了6个请求(峰值速度:burst+原来的处理速度)。对于剩下的4个请求,直接返回503,在下一秒如果继续向服务端发送10个请求,服务端会直接拒绝这10个请求并返回503。因为设定了没6s处理1个请求,所以直到30 s 之后,才可以再处理一个请求,即如果此时向服务端发送10个请求,会返回9个503,一个200
  • 查看/var/log/nginx/error.log日志,发现有4个请求被直接拒绝了,没有延时请求。

 

 

总结:

  • limit_req zone=req_zone;
    • 严格依照在limti_req_zone中配置的rate来处理请求
    • 超过rate处理能力范围的,直接drop
    • 表现为对收到的请求无延时
  • limit_req zone=req_zone burst=5;
    • 依照在limti_req_zone中配置的rate来处理请求
    • 同时设置了一个大小为5的缓冲队列,在缓冲队列中的请求会等待慢慢处理
    • 超过了burst缓冲队列长度和rate处理能力的请求被直接丢弃
    • 表现为对收到的请求有延时
  • limit_req zone=req_zone burst=5 nodelay;
    • 依照在limti_req_zone中配置的rate来处理请求
    • 同时设置了一个大小为5的缓冲队列,当请求到来时,会爆发出一个峰值处理能力,对于峰值处理数量之外的请求,直接丢弃
    • 在完成峰值请求之后,缓冲队列不能再放入请求。如果rate=10r/m,且这段时间内没有请求再到来,则每6 s 缓冲队列就能回复一个缓冲请求的能力,直到回复到能缓冲5个请求位置。

 

转载请注明:SuperIT » Nginx下limit_req模块burst参数超详细解析

喜欢 (0)or分享 (0)