博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅谈限流(下)实战
阅读量:4565 次
发布时间:2019-06-08

本文共 5017 字,大约阅读时间需要 16 分钟。

常见的应用限流手段

应用开发中常见的限流的都有哪些呢?其实常用的限流手段都比较简单,关键都是限流服务的高并发。为了在LB上实现高效且有效的限流,普遍的做法都是Nginx+Lua或者Nginx+Redis去实现服务服务限流,所以市面上比较常用的waf框架都是基于Openresty去实现的。我们看下比较常用的几个限流方式。

Openresty+共享内存实现的计数限流

先看下代码限流代码

lua_shared_dict limit_counter 10m;server {listen 80;server_name www.test.com;location / {root html;index index.html index.htm;}location /test {access_by_lua_block {local function countLimit()local limit_counter =ngx.shared.limit_counterlocal key = ngx.var.remote_addr .. ngx.var.http_user_agent .. ngx.var.uri .. ngx.var.hostlocal md5Key = ngx.md5(key)local limit = 10local exp = 300local current =limit_counter:get(key)if current ~= nil and current + 1> limit thenreturn 1endif current == nil thenlimit_counter:add(key, 1, exp)elselimit_counter:incr(key, 1)endreturn 0endlocal ret = countLimit()if ret > 0 thenngx.exit(405)end}content_by_lua 'ngx.say(111)';}}

解释下上面这段简单的代码,对于相同的IP UA HOST URI组合的唯一KEY,就是同一个URI每个用户在5分钟内只允许有10次请求,如果超过10次请求,就返回405的状态码,如果小于10次,就继续执行后面的处理阶段。

看下访问结果

curlhttp://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test405 Not Allowed

405 Not Allowed


openresty/1.13.6.2

这就是一个简单的计数限流的例子

Openresty 限制连接数和请求数的模块

限制连接数和请求数的模块是 lua-resty-limit-traffic。它的限速实现基于以前说过的漏桶原理。

蓄水池一边注水一边放水的问题。 这里注水的速度是新增请求/连接的速度,而放水的速度则是配置的限制速度。 当注水速度快于放水速度(表现为池中出现蓄水),则返回一个数值 delay。调用者通过 ngx.sleep(delay) 来减慢注水的速度。 当蓄水池满时(表现为当前请求/连接数超过设置的 burst 值),则返回错误信息 rejected。调用者需要丢掉溢出来的这部份。
看下配置代码

http {lua_shared_dict my_req_store 100m;lua_shared_dict my_conn_store 100m;server {location / {access_by_lua_block {local limit_conn = require "resty.limit.conn"local limit_req = require "resty.limit.req"local limit_traffic = require "resty.limit.traffic"local lim1, err = limit_req.new("my_req_store", 300, 150)--300r/s的频率,大于300小于450就延迟大概0.5秒,超过450的请求就返回503错误码local lim2, err = limit_req.new("my_req_store", 200, 100)local lim3, err = limit_conn.new("my_conn_store", 1000, 1000, 0.5)--1000c/s的频率,大于1000小于2000就延迟大概1s,超过2000的连接就返回503的错误码,估算每个连接的时间大概是0.5秒,local limiters = {lim1, lim2, lim3}local host = ngx.var.hostlocal client = ngx.var.binary_remote_addrlocal keys = {host, client, client}local states = {}local delay, err = limit_traffic.combine(limiters, keys, states)if not delay thenif err == "rejected" thenreturn ngx.exit(503)endngx.log(ngx.ERR, "failed to limit traffic: ", err)return ngx.exit(500)endif lim3:is_committed() thenlocal ctx = ngx.ctxctx.limit_conn = lim3ctx.limit_conn_key = keys[3]endprint("sleeping ", delay, " sec, states: ",table.concat(states, ", "))if delay >= 0.001 thenngx.sleep(delay)end}log_by_lua_block {local ctx = ngx.ctxlocal lim = ctx.limit_connif lim thenlocal latency = tonumber(ngx.var.request_time)local key = ctx.limit_conn_keylocal conn, err = lim:leaving(key, latency)if not conn thenngx.log(ngx.ERR,"failed to record the connection leaving ","request: ", err)returnendend}}}}

简单的注释可以介绍它大概的参数说明了。具体的可以参看下官方文档

注意下,连接数限流在log阶段有个leaving()的调用来动态调整请求时间。不要忘记leaving的调用
用了这么长时间了没感觉有啥需要注意的坑。就是测试的时候,要测出效果,需要ngx.sleep下,否则,简单的程序,没任何压力,Nginx都能执行完,不会有延迟。所以需要测试延迟的时候 content阶段做下sleep,就能测到效果了。

Openresty 共享内存 动态限流

我们的使用的过程中发现,攻击或者流量打过来的时候我通常的流程都是:先通过日志服务发现有流量,然后在查询攻击的IP 或者UID,最后再封禁这些IP或者UID。一直是滞后的。我们应该做的是,在流量进来的时候通过动态分析直接拦截,而不是滞后拦截,滞后拦截有可能服务都被流量打死了。

动态限流是基于前面的技术限流的。

lua_shared_dict limit_counter 10m;server {listen 80;server_name www.test.com; location / {root html;index index.html index.htm;}location /test {access_by_lua_block {local function countLimit()local limit_counter =ngx.shared.limit_counterlocal key = ngx.var.remote_addr .. ngx.var.http_user_agent .. ngx.var.uri .. ngx.var.hostlocal md5Key = ngx.md5(key)local limit = 5local exp = 120local disable = 7200local disableKey = md5Key .. ":disable"local disableRt = limit_counter:get(disableKey)if disableRt thenreturn 1endlocal current =limit_counter:get(key)if current ~= nil and current + 1> limit thendict:set(disableKey, 1, disable)return 1endif current == nil thenlimit_counter:add(key, 1, exp)elselimit_counter:incr(key, 1)endreturn 0endlocal ret = countLimit()if ret > 0 thenngx.exit(405)end}content_by_lua 'ngx.say(111)';}}

看下这行结果

curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test111curl http://www.test.com/test500 Internal Server Error

500 Internal Server Error


openresty/1.13.6.2

大致的思路比较简单,一旦发现请求触发阀值(2分钟5次),直接将请求的唯一值放到黑名单2个小时,以后的请求一旦发现在黑名单里面,就直接返回503。如果没有触发阀值,那就给请求的唯一值加1,这个计数器的过期时间是2分钟,过了两分钟就会重新计数。基本满足了我们目前当前的动态限流。

最后

我目前工作中比较常见的限流方式就上面三种,第二种是oenresty官方的模块,已经能够满足绝大多数限流需求,达到保护服务的目的。简单的限流控制利用openresty+shared.DICT很容易实现,把shared.DICT换成Redis就可以实现分布式限流。当然了,市场上已经有了很多特别优秀的开源的网关服务框架包含了waf的功能,使用比较多的比如kong、orange,已经有很多巨头公司在使用了,最近比较热门的apisix等等。如果有这方面需求的话可以关注下。

转载于:https://www.cnblogs.com/feixiangmanon/p/11495314.html

你可能感兴趣的文章
mybatis association和collection标签怎么用
查看>>
一个最简单的Delphi2010的PNG异形窗口方法
查看>>
怎样关闭拼写错误标记
查看>>
常见COM问题解答
查看>>
用到的linux命令
查看>>
【词云】代码
查看>>
201521123038 《Java程序设计》 第十二周学习总结
查看>>
UIImageView 一些属性设置
查看>>
C# winfrom 打印到Excel中
查看>>
Java中ThreadLocal类的作用以及实现原理
查看>>
mustache学习补遗
查看>>
MVC4 View 的呈现
查看>>
HTML5的自定义属性data-*详细介绍和JS操作实例
查看>>
LINQ:是BUG还是~~~
查看>>
python文件操作 seek(),tell()
查看>>
数据库优化方法
查看>>
联想梁军加盟乐视网任副总裁
查看>>
水平垂直居中
查看>>
十九、扩展 Extensions
查看>>
golang批量修改文件名
查看>>