随着应用的访问量愈来愈高,瞬时流量不可预估,为了保证服务对外的稳定性,限流成为每一个应用必备的一道安全防火墙,即便普通的用户也会常常遇到,如微博的限流,抖音的限流,小米抢购的限流......若是没有这道安全防火墙,请求的流量超过服务的负载能力,很容易形成整个服务的瘫痪。
限流须要提早评估好,若是用的不当,可能会致使有些该限制的流量没有被限流,服务被这些过载流量打垮。有些不应限制流量的被限制,被用户抱怨。例如,总体服务的QPS是400/s,若是限流阀值是300,就会致使每秒有100个请求本该接受服务,却被限制访问,若是阀值是500,就会致使每秒有100个请求负载,时间越长累积越多,这些过载的流量就有可能致使整个服务的瘫痪。node
常见的限流算法有令牌同、漏桶,还有一种计数器。web
令牌算法的过程以下算法
若是令牌桶中少于n个令牌,那么不会删除令牌,而且认为这个数据包在流量限制以外,要不丢弃要不缓冲区等待
后端
两个算法实现同样,方向相反,令牌是匀速流入,流通是匀速流出。安全
#### 计数器
计数器比较简单,没有什么算法和描述。知足必定的条件的流量计数加1,达到阀值了限制,顾名思义叫计数限流。服务器
使用最多见的就是Nginx自带两个限流模块:链接数限流模块ngx_http_limit_conn_module 和请求数限流模块ngx_http_limit_req_module;还有openresty的限流模块lua-resty-limit-traffic;还可能须要应对复杂的业务需求而自研的计数限流。咱们一一介绍下这些限流方法的使用网络
从名字就能够看出是Nginx的链接数限流。大多都是按照IP来源进行链接数限流,也能够按照域名对总的链接数进行限流。
咱们看下链接数限流的配置性能
http { limit_conn_zone $binary_remote_addr zone=addr:10m; limit_conn_log_level error; limit_conn_status 503; ... server { ... location /download/ { limit_conn addr 1; }
limit_conn_zone: 配置限流的key以及存储这些key共用的共享内存的大小;
样例中的key 是$binary_remote_addr,表示IP地址,若是若是须要对总域名进行限流,key就应该使用 $server_name $host等等,能惟一表示该域名的Nginx变量;
zone=addr:10m中,addr表示链接数限流的区域名称,10m表示能够分配的共享空间的大小。
binary_remote_addr变量在64位平台中占用64字节。1M共享空间能够保存1.6万个64位的,10m就能够保存16万个。若是超过16万个,共享空间被用完,服务器将会对后续全部的请求返回 503。
limit_conn:配置指定key的最大链接数。样例中指定的最大链接数是1,表示Nginx最多同时容许1个链接进行location /limit 的行为操做。
limit_conn_status:配置被限流后返回的状态码,样例中设置的是503.
limit_conn_log_level:配置被限流后的日志级别,设置的是error级别
看下测试代码测试
limit_conn_zone $server_name zone=addr:10m; limit_conn_log_level error; limit_conn_status 503; server{ listen 80; server_name test.test.com; access_log /var/log/openresty/web_test.test.com_access.log test; error_log /var/log/openresty/web_test.test.com_error.log; location /test/ { limit_conn addr 2; content_by_lua ' ngx.sleep(1) ngx.say("helloworld") '; } } ab 命令 ab -n10 -c3 http://test.test.com/test/ access_log 127.0.0.1|1553438999.158|200 127.0.0.1|1553438999.160|503 127.0.0.1|1553438999.160|503 127.0.0.1|1553438999.161|503 127.0.0.1|1553438999.162|503 127.0.0.1|1553438999.163|503 127.0.0.1|1553438999.163|503 127.0.0.1|1553438999.164|503 127.0.0.1|1553439000.160|200 127.0.0.1|1553439000.160|200 error_log 2019/03/24 22:49:59 [error] 700#0: *63 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *64 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *65 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *66 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *67 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *68 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/24 22:49:59 [error] 700#0: *69 limiting connections by zone "addr", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com"
能够看到是符合咱们配置语气的。若是咱们将
limit_conn_log_level info;
limit_conn_status 500;
能够看到,error_log里面记录的日志就是info的,固然error_log的级别要调到info级别。返回的HTTP状态码也会变为500.能够动手试下。lua
limit_conn 的执行过程
请求进入首先判判定义的key的链接数是否超过limit_conn配置的阀值,若是超过直接返回limit_conn_status定义的错误码;若是没有超过链接数+1
请求处理
请求处理完成以后链接数-1
这就是为何要作下sleep操做,不然在测试环境下没有任何压力,两个链接数彻底能够在一秒以内处理完10个请求。为了测试出效果,就须要在一秒以内让Nginx没法完成10个请求。
Nginx的请求数限流,请求数限流是漏桶算法实现的。经过定义的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 /search/ { limit_req zone=one burst=5; }
limit_req_zone:配置限流的key,存放key对应的共享区域空间大小,固定的请求速率。样例中的key binary_remote_addr 表示IP地址。one 表示共享区域空间的名称,10m表示共享区域空间的大小,跟limit_conn的定义一致,10m就能够保存16万个IP地址。rate=1r/s 固定请求速率设置,每秒1个请求。
limit_req:配置限流区域,桶容量,是否延迟模式。样例中桶容量是5,延迟模式默认是延迟。
limit_req_status:配置被限流后返回的状态。样例中是503
limit_req_log_level:配置被限流后的日志级别,样例中是error
测试下上面的代码
看下测试代码
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; limit_req_log_level error; limit_req_status 503; server{ listen 80; server_name test.test.com; access_log /var/log/openresty/web_test.test.com_access.log test; error_log /var/log/openresty/web_test.test.com_error.log info; location /test/ { limit_req zone=one burst=5; content_by_lua ' ngx.say("helloworld") '; } } ab 命令 ab -n10 -c10 http://test.test.com/test/ access_log 127.0.0.1|1553525058.469|200 127.0.0.1|1553525058.470|503 127.0.0.1|1553525058.470|503 127.0.0.1|1553525058.470|503 127.0.0.1|1553525058.470|503 127.0.0.1|1553525059.471|200 127.0.0.1|1553525060.470|200 127.0.0.1|1553525061.470|200 127.0.0.1|1553525062.471|200 127.0.0.1|1553525063.471|200 error_log 2019/03/25 22:44:18 [warn] 833#0: *144 delaying request, excess: 0.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [warn] 833#0: *145 delaying request, excess: 1.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [warn] 833#0: *146 delaying request, excess: 2.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [warn] 833#0: *147 delaying request, excess: 3.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [warn] 833#0: *148 delaying request, excess: 4.999, by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [error] 833#0: *149 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [error] 833#0: *150 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [error] 833#0: *151 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com" 2019/03/25 22:44:18 [error] 833#0: *152 limiting requests, excess: 5.999 by zone "one", client: 127.0.0.1, server: test.test.com, request: "GET /test/ HTTP/1.0", host: "test.test.com"
测试代码中桶容量是5,按照1r/s的速度处理。能够看到在,因为默认是延迟模式,因此1553525059.471到1553525063.471这个时间段最多存储5个请求,而后按照1r/s的速度处理,因为延迟模式,error_log能够看到这五条记录都是延迟执行的(delaying request)。大于五条的记录都限流503 了。
那为何第一条记录执行成功了?这应该是计算算法的问题,第一条记录没有参考值,因此第一秒没有计算在内,这以后的都是按照第一条记录参考的时间,因此后面的基本上都是精确的。
咱们将延迟模式改成不延迟模式看下。
location /test/ { limit_req zone=one burst=5 nodelay; content_by_lua ' ngx.say("helloworld") '; } ab 测试 ab -n7 -c7 http://test.test.com/test/ ab -n7 -c7 http://test.test.com/test/ ab -n7 -c7 http://test.test.com/test/ access_log 127.0.0.1|1554385661.861|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|200 127.0.0.1|1554385661.862|503 127.0.0.1|1554385665.513|200 127.0.0.1|1554385665.514|200 127.0.0.1|1554385665.514|200 127.0.0.1|1554385665.514|503 127.0.0.1|1554385665.514|503 127.0.0.1|1554385665.514|503 127.0.0.1|1554385665.514|503 127.0.0.1|1554385667.361|200 127.0.0.1|1554385667.361|200 127.0.0.1|1554385667.362|503 127.0.0.1|1554385667.362|503 127.0.0.1|1554385667.362|503 127.0.0.1|1554385667.362|503 127.0.0.1|1554385667.362|503
咱们为了跨时间窗口测试,咱们测试三组。先看下第一组,7个请求6个成功,一个503,其实理论上桶容量是5,至多只可能成功5个,有个503才对。咱们说了第一组计算算法问题基本上忽略的。
咱们看下第二组,跟第一组相差4秒,处理速度是1r/s.4秒以后按按理应该桶里有4个位置,应该成功处理4个,3个503,怎么如今是4个503,成功处理三个,此处仍是要强调下limit_req的实现算法不是特别精确
咱们看下第三组,比第二组晚了2秒,因此桶里会有2个位置,应该有2个请求成功,5个请求503.这个跟预想的基本吻合。
因此总体上和理解是一致的。就是算法上不是特别的精确。咱们生产上限流也是至少几千几万的限流,算法上的精确差别实际上是能够忽略不计的。
这一部分主要是聊了下限流的原理和常见的Nginx的两个限流模块。下一部分咱们聊下生产中比较常见的lua限流。
------------------------------------end
一块儿关注高性能WEB后端技术,关注公众号