Nginx接入层限流降级方案

                                         

Nginx 接入层限流方案html

 

     接入层一般指请求的入口,该层的主要目的有:负载均衡、非法请求过滤、请求聚合、缓存、降级、限流、A/B测试、服务器质量监控等等。node

     根据具体业务场景,限流措施咱们选用Nginx 自带的两个模块:链接数限流模块ngx_http_limit_conn_module 和漏桶算法实现请求限流模块ngx_http_limit_req_modulenginx

 

     limit_conn用来对某个KEY对应的总的网络链接数进行限流,能够按照如IP、域名维度进行限流。limit_req用来对某个KEY对应的请求的平均速率进行限流,并有两种用法:平滑模式(delay)和容许突发模式(nodelay)算法

 

ngx_http_limit_conn_moduleapi

 

    limit_conn是对某个KEY对应的总的网络链接数进行限流。能够按照IP来限制IP维度的总链接数,或者按照服务域名来限制某个域名的总链接数。可是记住不是每个请求链接都会被计数器统计,只有那些被Nginx处理的且已经读取了整个请求头的请求链接才会被计数器统计。缓存

 

 

http {服务器

 

#Test ngx_http_limit_conn_module网络

 

    limit_conn_zone $binary_remote_addr zone=addr:10m;并发

 

    limit_conn_log_level error;负载均衡

 

    limit_conn_status 508;

 

 

 

#server

 

    server {

 

        listen       80;

 

        server_name  10.6.8.123;

 

        server_name_in_redirect off;

 

        fastcgi_intercept_errors on;

 

        index index.htm index.html;

 

        

 

        access_log  logs/10.6.8.123_access.log access ; 

 

       

 

        location /limit {

 

            limit_conn addr 2;

 

        alias   /tmp/;

 

        }

 

     }

 

}

 

 

 

limit_conn:要配置存放KEY和计数器的共享内存区域和指定KEY的最大链接数;此处指定的最大链接数是1,表示Nginx最多同时并发处理2个链接;

limit_conn_zone:用来配置限流KEY、及存放KEY对应信息的共享内存区域大小;此处的KEY是“$binary_remote_addr”其表示IP地址,也可使用如$server_name做为KEY来限制域名级别的最大链接数;

limit_conn_status:配置被限流后返回的状态码,默认返回503

limit_conn_log_level:配置记录被限流后的日志级别,默认error级别。

 

limit_conn的主要执行过程以下所示:

1、请求进入后首先判断当前limit_conn_zone中相应KEY的链接数是否超出了配置的最大链接数;

2.1、若是超过了配置的最大大小,则被限流,返回limit_conn_status定义的错误状态码;

2.2、不然相应KEY的链接数加1,并注册请求处理完成的回调函数;

3、进行请求处理;

4、在结束请求阶段会调用注册的回调函数对相应KEY的链接数减1

 

limt_conn能够限流某个KEY的总并发/请求数,KEY能够根据须要变化。

 

经过ab测试工具测试:ab -n 5 -c 5 http://10.6.8.123/limit/ 

image2017-1-5%2011%3A12%3A9.png?version=1&modificationDate=1483585904000&api=v2

 

按照IP限制并发链接数配置示例:

 

首先定义IP纬度的限流区域:

 

limit_conn_zone $binary_remote_addr zone=perip:10m;

接着在要限流的location中添加限流逻辑:

 

location /limit {

 

     limit_conn perip 2;

 

     alias   /tmp/;

 

     }

使用AB测试工具进行测试,并发数为5个,总的请求数为5个:

将获得以下access.log输出:

[08/Jun/2016:20:10:51+0800] [1465373451.802] 200

[08/Jun/2016:20:10:51+0800] [1465373451.803] 200

[08/Jun/2016:20:10:51 +0800][1465373451.803] 503

[08/Jun/2016:20:10:51 +0800][1465373451.803] 503

[08/Jun/2016:20:10:51 +0800][1465373451.803] 503

 

此处咱们把access log格式设置为log_format main  '[$time_local] [$msec] $status';分别是“日期 日期秒/毫秒值 响应状态码”。

 

若是被限流了,则在error.log中会看到相似以下的内容:

2016/06/08 20:10:51 [error] 5662#0: *5limiting connections by zone "perip", client: 127.0.0.1, server: _,request: "GET /limit HTTP/1.0", host: "localhost"

 

 

 

ngx_http_limit_req_module

 

limit_req是漏桶算法实现,用于对指定KEY对应的请求进行限流,好比按照IP维度限制请求速率。

 

配置示例:

http {

    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

    limit_req_log_level error;

    limit_req_status 503;

    ...

    server {

    ...

    location /limit {

        limit_req zone=one burst=5 nodelay;

    }

limit_req:配置限流区域、桶容量(突发容量,默认0)、是否延迟模式(默认延迟);

limit_req_zone:配置限流KEY、及存放KEY对应信息的共享内存区域大小、固定请求速率;此处指定的KEY是“$binary_remote_addr”表示IP地址;固定请求速率使用rate参数配置,支持10r/s60r/m,即每秒10个请求和每分钟60个请求,不过最终都会转换为每秒的固定请求速率(10r/s为每100毫秒处理一个请求;60r/m,即每1000毫秒处理一个请求)。

limit_conn_status:配置被限流后返回的状态码,默认返回503

limit_conn_log_level:配置记录被限流后的日志级别,默认error级别。

 

注意使用$binary_remote_addr ,此变量在32位服务器上面占用32字节,在64 位服务器上占用64字节,所以,前面设置10m的zone,在32位服务器上面就可以容纳320000个状态,在64位服务器上面就能容纳160000个状态。

 

limit_req的主要执行过程以下所示:

1、请求进入后首先判断最后一次请求时间相对于当前时间(第一次是0)是否须要限流,若是须要限流则执行步骤2,不然执行步骤3

2.1、若是没有配置桶容量(burst),则桶容量为0;按照固定速率处理请求;若是请求被限流,则直接返回相应的错误码(默认503);

2.2、若是配置了桶容量(burst>0)且延迟模式(没有配置nodelay);若是桶满了,则新进入的请求被限流;若是没有满则请求会以固定平均速率被处理(按照固定速率并根据须要延迟处理请求,延迟使用休眠实现);

2.3、若是配置了桶容量(burst>0)且非延迟模式(配置了nodelay);不会按照固定速率处理请求,而是容许突发处理请求;若是桶满了,则请求被限流,直接返回相应的错误码;

3、若是没有被限流,则正常处理请求;

4Nginx会在相应时机进行选择一些(3个节点)限流KEY进行过时处理,进行内存回收。

 

场景2.1测试

首先定义IP维度的限流区域:

limit_req_zone $binary_remote_addr zone=test:10m rate=500r/s;

限制为每秒500个请求,固定平均速率为2毫秒一个请求。

 

接着在要限流的location中添加限流逻辑:

location /limit {

    limit_req zone=test;

    echo "123";

}

即桶容量为0burst默认为0),且延迟模式。

 

使用AB测试工具进行测试,并发数为2个,总的请求数为10个:

ab -n 10 -c 2 http://localhost/limit

                 

将获得以下access.log输出:

[08/Jun/2016:20:25:56+0800] [1465381556.410] 200

[08/Jun/2016:20:25:56 +0800][1465381556.410] 503

[08/Jun/2016:20:25:56 +0800][1465381556.411] 503

[08/Jun/2016:20:25:56+0800] [1465381556.411] 200

[08/Jun/2016:20:25:56 +0800][1465381556.412] 503

[08/Jun/2016:20:25:56 +0800][1465381556.412] 503

 

虽然每秒容许500个请求,可是由于桶容量为0,因此流入的请求要么被处理要么被限流,没法延迟处理;另外平均速率在2毫秒左右,好比1465381556.4101465381556.411被处理了;有朋友会说这固定平均速率不是1毫秒嘛,其实这是由于实现算法没那么精准形成的。

 

若是被限流在error.log中会看到以下内容:

2016/06/08 20:25:56 [error] 6130#0: *1962limiting requests, excess: 1.000 by zone "test", client: 127.0.0.1,server: _, request: "GET /limit HTTP/1.0", host:"localhost"

 

若是被延迟了在error.log(日志级别要INFO级别)中会看到以下内容:

2016/06/10 09:05:23 [warn] 9766#0: *97021delaying request, excess: 0.368, by zone "test", client: 127.0.0.1,server: _, request: "GET /limit HTTP/1.0", host:"localhost"

 

场景2.2测试

首先定义IP维度的限流区域:

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;

为了方便测试设置速率为每秒2个请求,即固定平均速率是500毫秒一个请求。

 

接着在要限流的location中添加限流逻辑:

location /limit {

    limit_req zone=test burst=3;

    echo "123";

}

固定平均速率为500毫秒一个请求,通容量为3,若是桶满了新的请求被限流,不然能够进入桶中排队并等待(实现延迟模式)。

 

为了看出限流效果咱们写了一个req.sh脚本:

ab -c 6 -n 6 http://localhost/limit

sleep 0.3

ab -c 6 -n 6 http://localhost/limit

首先进行6个并发请求6URL,而后休眠300毫秒,而后再进行6个并发请求6URL;中间休眠目的是为了能跨越2秒看到效果,若是看不到以下的效果能够调节休眠时间。

 

将获得以下access.log输出:

[09/Jun/2016:08:46:43+0800] [1465433203.959] 200

[09/Jun/2016:08:46:43 +0800][1465433203.959] 503

[09/Jun/2016:08:46:43 +0800][1465433203.960] 503

[09/Jun/2016:08:46:44+0800] [1465433204.450] 200

[09/Jun/2016:08:46:44+0800] [1465433204.950] 200

[09/Jun/2016:08:46:45 +0800][1465433205.453] 200

 

[09/Jun/2016:08:46:45 +0800][1465433205.766] 503

[09/Jun/2016:08:46:45 +0800][1465433205.766] 503

[09/Jun/2016:08:46:45 +0800][1465433205.767] 503

[09/Jun/2016:08:46:45+0800] [1465433205.950] 200

[09/Jun/2016:08:46:46+0800] [1465433206.451] 200

[09/Jun/2016:08:46:46+0800] [1465433206.952] 200

 

image2017-1-5%2011%3A12%3A49.png?version=1&modificationDate=1483585944000&api=v2

桶容量为3,即桶中在时间窗口内最多流入3个请求,且按照2r/s的固定速率处理请求(即每隔500毫秒处理一个请求);桶计算时间窗口(1.5秒)=速率(2r/s/桶容量(3),也就是说在这个时间窗口内桶最多暂存3个请求。所以咱们要以当前时间往前推1.5秒和1秒来计算时间窗口内的总请求数;另外由于默认是延迟模式,因此时间窗内的请求要被暂存到桶中,并以固定平均速率处理请求:

第一轮:有4个请求处理成功了,按照漏桶桶容量应该最多3个才对;这是由于计算算法的问题,第一次计算因没有参考值,因此第一次计算后,后续的计算才能有参考值,所以第一次成功能够忽略;这个问题影响很小能够忽略;并且按照固定500毫秒的速率处理请求。

第二轮:由于第一轮请求是突发来的,差很少都在1465433203.959时间点,只是由于漏桶将速率进行了平滑变成了固定平均速率(每500毫秒一个请求);而第二轮计算时间应基于1465433203.959;而第二轮突发请求差很少都在1465433205.766时间点,所以计算桶容量的时间窗口应基于1465433203.9591465433205.766来计算,计算结果为1465433205.766这个时间点漏桶为空了,能够流入桶中3个请求,其余请求被拒绝;又由于第一轮最后一次处理时间是1465433205.453,因此第二轮第一个请求被延迟到了1465433205.950。这里也要注意固定平均速率只是在配置的速率左右,存在计算精度问题,会有一些误差。

 

若是桶容量改成1burst=1),执行req.sh脚本能够看到以下输出:

09/Jun/2016:09:04:30+0800] [1465434270.362] 200

[09/Jun/2016:09:04:30 +0800][1465434270.371] 503

[09/Jun/2016:09:04:30 +0800] [1465434270.372]503

[09/Jun/2016:09:04:30 +0800][1465434270.372] 503

[09/Jun/2016:09:04:30 +0800][1465434270.372] 503

[09/Jun/2016:09:04:30+0800] [1465434270.864] 200

 

[09/Jun/2016:09:04:31 +0800][1465434271.178] 503

[09/Jun/2016:09:04:31 +0800][1465434271.178] 503

[09/Jun/2016:09:04:31 +0800][1465434271.178] 503

[09/Jun/2016:09:04:31 +0800][1465434271.178] 503

[09/Jun/2016:09:04:31 +0800][1465434271.179] 503

[09/Jun/2016:09:04:31+0800] [1465434271.366] 200

桶容量为1,按照每1000毫秒一个请求的固定平均速率处理请求。

 

场景2.3测试

首先定义IP维度的限流区域:

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;

为了方便测试配置为每秒2个请求,固定平均速率是500毫秒一个请求。

 

接着在要限流的location中添加限流逻辑:

location /limit {

    limit_req zone=test burst=3 nodelay;

    echo "123";

}

桶容量为3,若是桶满了直接拒绝新请求,且每秒2最多两个请求,桶按照固定500毫秒的速率以nodelay模式处理请求。

 

为了看到限流效果咱们写了一个req.sh脚本:

ab -c 6 -n 6 http://localhost/limit

sleep 1

ab -c 6 -n 6 http://localhost/limit

sleep 0.3

ab -c 6 -n 6 http://localhost/limit

sleep 0.3

ab -c 6 -n 6 http://localhost/limit

sleep 0.3

ab -c 6 -n 6 http://localhost/limit

sleep 2

ab -c 6 -n 6 http://localhost/limit

 

将获得相似以下access.log输出:

[09/Jun/2016:14:30:11+0800] [1465453811.754] 200

[09/Jun/2016:14:30:11+0800] [1465453811.755] 200

[09/Jun/2016:14:30:11+0800] [1465453811.755] 200

[09/Jun/2016:14:30:11+0800] [1465453811.759] 200

[09/Jun/2016:14:30:11 +0800][1465453811.759] 503

[09/Jun/2016:14:30:11 +0800][1465453811.759] 503

 

[09/Jun/2016:14:30:12+0800] [1465453812.776] 200

[09/Jun/2016:14:30:12+0800] [1465453812.776] 200

[09/Jun/2016:14:30:12 +0800][1465453812.776] 503

[09/Jun/2016:14:30:12 +0800][1465453812.777] 503

[09/Jun/2016:14:30:12 +0800][1465453812.777] 503

[09/Jun/2016:14:30:12 +0800][1465453812.777] 503

 

[09/Jun/2016:14:30:13 +0800] [1465453813.095]503

[09/Jun/2016:14:30:13 +0800][1465453813.097] 503

[09/Jun/2016:14:30:13 +0800][1465453813.097] 503

[09/Jun/2016:14:30:13 +0800][1465453813.097] 503

[09/Jun/2016:14:30:13 +0800][1465453813.097] 503

[09/Jun/2016:14:30:13 +0800][1465453813.098] 503

 

[09/Jun/2016:14:30:13+0800] [1465453813.425] 200

[09/Jun/2016:14:30:13 +0800][1465453813.425] 503

[09/Jun/2016:14:30:13 +0800][1465453813.425] 503

[09/Jun/2016:14:30:13 +0800][1465453813.426] 503

[09/Jun/2016:14:30:13 +0800][1465453813.426] 503

[09/Jun/2016:14:30:13 +0800][1465453813.426] 503

 

[09/Jun/2016:14:30:13+0800] [1465453813.754] 200

[09/Jun/2016:14:30:13 +0800][1465453813.755] 503

[09/Jun/2016:14:30:13 +0800][1465453813.755] 503

[09/Jun/2016:14:30:13 +0800][1465453813.756] 503

[09/Jun/2016:14:30:13 +0800][1465453813.756] 503

[09/Jun/2016:14:30:13 +0800][1465453813.756] 503

 

[09/Jun/2016:14:30:15+0800] [1465453815.278] 200

[09/Jun/2016:14:30:15+0800] [1465453815.278] 200

[09/Jun/2016:14:30:15+0800] [1465453815.278] 200

[09/Jun/2016:14:30:15 +0800][1465453815.278] 503

[09/Jun/2016:14:30:15 +0800][1465453815.279] 503

[09/Jun/2016:14:30:15 +0800][1465453815.279] 503

 

[09/Jun/2016:14:30:17+0800] [1465453817.300] 200

[09/Jun/2016:14:30:17+0800] [1465453817.300] 200

[09/Jun/2016:14:30:17+0800] [1465453817.300] 200

[09/Jun/2016:14:30:17+0800] [1465453817.301] 200

[09/Jun/2016:14:30:17 +0800][1465453817.301] 503

[09/Jun/2016:14:30:17 +0800][1465453817.301] 503

 

 image2017-1-5%2011%3A13%3A8.png?version=1&modificationDate=1483585963000&api=v2

 

桶容量为3(,即桶中在时间窗口内最多流入3个请求,且按照2r/s的固定速率处理请求(即每隔500毫秒处理一个请求);桶计算时间窗口(1.5秒)=速率(2r/s/桶容量(3),也就是说在这个时间窗口内桶最多暂存3个请求。所以咱们要以当前时间往前推1.5秒和1秒来计算时间窗口内的总请求数;另外由于配置了nodelay,是非延迟模式,因此容许时间窗内突发请求的;另外从本示例会看出两个问题:

第一轮和第七轮:有4个请求处理成功了;这是由于计算算法的问题,本示例是若是2秒内没有请求,而后接着忽然来了不少请求,第一次计算的结果将是不正确的;这个问题影响很小能够忽略;

第五轮:1.0秒计算出来是3个请求;此处也是因计算精度的问题,也就是说limit_req实现的算法不是很是精准的,假设此处当作相对于2.75的话,1.0秒内只有1次请求,因此仍是容许1次请求的。

 

若是限流出错了,能够配置错误页面:

proxy_intercept_errors on;

recursive_error_pages on;

error_page 503 //www.jd.com/error.aspx;

limit_conn_zone/limit_req_zone定义的内存不足,则后续的请求将一直被限流,因此须要根据需求设置好相应的内存大小。

 

此处的限流都是单Nginx的,假设咱们接入层有多个nginx,此处就存在和应用级限流相同的问题;那如何处理呢?一种解决办法:创建一个负载均衡层将按照限流KEY进行一致性哈希算法将请求哈希到接入层Nginx上,从而相同KEY的将打到同一台接入层Nginx上;另外一种解决方案就是使用Nginx+LuaOpenResty)调用分布式限流逻辑实现

相关文章
相关标签/搜索