nginx lua 限制流量脚步

[+]

防刷的概念:

防刷的目的是为了防止有些IP来爬去我们的网页,获取我们的价格等信息。不像普通的搜索引擎,这种爬去行为我们经过统计最高每秒300次访问,平均每秒266次访问。
由于我们的网站的页面都在CDN上,导致我们的CDN流量会定时冒尖。为了防止这种情况,打算将网页页面的访问从CDN切回主站。同时开启防刷功能,目前设置一秒200次访问即视为非法,会阻止10分钟的访问。

限流的概念:

限流的目的是在大促或者流量突增期间,我们的后端服务假设某个接口能够扛住的的QPS为10000,这时候同时有20000个请求进来,经过限流模块,会先放10000个请求,其余的请求会阻塞一段时间。不简单粗暴的返回404,让客户端重试,同时又能起到流量销峰的作用。

目前防刷模块已经经过ab的压测。
限流模块经过测试后发现,请求几乎很平均的按照限流的模式进行分布,不过会有接近1%的请求超时。因为极端情况下,一个请求总是被阻塞。(目前想到的解决方案:一个请求被阻塞多次后就放行,不再需要判断当前总请求数。)
redis部署方式:
单docker实例,由marathon负责调度,无需开启rdb和aof
风险:
 redis挂了。 处理方式:直接放行。 同时,我们的mesos能够保证redis在秒级内重启。
在限流模块的时候采用了redis的eval命令来进行原子的执行,而防刷模块没有。

下面放出代码,请各位大拿指正。close_redis的代码抄自开涛的博客中相关内容。
  •  防刷代码

       
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. -- access_by_lua_file '/opt/ops/lua/access_limit.lua'  
  2. local function close_redis(red)  
  3.     if not red then  
  4.         return  
  5.     end  
  6.     --释放连接(连接池实现)  
  7.     local pool_max_idle_time = 10000 --毫秒  
  8.     local pool_size = 100 --连接池大小  
  9.     local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  
  10.   
  11.     if not ok then  
  12.         ngx_log(ngx_ERR, "set redis keepalive error : ", err)  
  13.     end  
  14. end  
  15.   
  16. local redis = require "resty.redis"  
  17. local red = redis:new()  
  18. red:set_timeout(1000)  
  19. local ip = "redis-ip"  
  20. local port = redis-port  
  21. local ok, err = red:connect(ip,port)  
  22. if not ok then  
  23.     return close_redis(red)  
  24. end  
  25.   
  26. local clientIP = ngx.req.get_headers()["X-Real-IP"]  
  27. if clientIP == nil then  
  28.    clientIP = ngx.req.get_headers()["x_forwarded_for"]  
  29. end  
  30. if clientIP == nil then  
  31.    clientIP = ngx.var.remote_addr  
  32. end  
  33.   
  34. local incrKey = "user:"..clientIP..":freq"  
  35. local blockKey = "user:"..clientIP..":block"  
  36.   
  37. local is_block,err = red:get(blockKey) -- check if ip is blocked  
  38. if tonumber(is_block) == 1 then  
  39.    ngx.exit(ngx.HTTP_FORBIDDEN)  
  40.    return close_redis(red)  
  41. end  
  42.   
  43. res, err = red:incr(incrKey)  
  44.   
  45. if res == 1 then  
  46.    res, err = red:expire(incrKey,1)  
  47. end  
  48.   
  49. if res > 200 then  
  50.     res, err = red:set(blockKey,1)  
  51.     res, err = red:expire(blockKey,600)  
  52. end  
  53.   
  54. close_redis(red)  

 
 
  •  限流代码

     
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. -- access_by_lua_file '/opt/ops/lua/access_flow_control.lua'  
  2. local function close_redis(red)  
  3.     if not red then  
  4.         return  
  5.     end  
  6.     --释放连接(连接池实现)  
  7.     local pool_max_idle_time = 10000 --毫秒  
  8.     local pool_size = 100 --连接池大小  
  9.     local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)  
  10.   
  11.     if not ok then  
  12.         ngx_log(ngx_ERR, "set redis keepalive error : ", err)  
  13.     end  
  14. end  
  15.   
  16. local function wait()  
  17.    ngx.sleep(1)  
  18. end  
  19.   
  20. local redis = require "resty.redis"  
  21. local red = redis:new()  
  22. red:set_timeout(1000)  
  23. local ip = "redis-ip"  
  24. local port = redis-port  
  25. local ok, err = red:connect(ip,port)  
  26. if not ok then  
  27.     return close_redis(red)  
  28. end  
  29.   
  30. local uri = ngx.var.uri -- 获取当前请求的uri  
  31. local uriKey = "req:uri:"..uri  
  32. res, err = red:eval("local res, err = redis.call('incr',KEYS[1]) if res == 1 then local resexpire, err = redis.call('expire',KEYS[1],KEYS[2]) end return (res)",2,uriKey,1)  
  33. while (res > 10)  
  34. do   
  35.    local twait, err = ngx.thread.spawn(wait)  
  36.    ok, threadres = ngx.thread.wait(twait)  
  37.    if not ok then  
  38.       ngx_log(ngx_ERR, "wait sleep error: ", err)  
  39.       break;  
  40.    end  
  41.    res, err = red:eval("local res, err = redis.call('incr',KEYS[1]) if res == 1 then local resexpire, err = redis.call('expire',KEYS[1],KEYS[2]) end return (res)",2,uriKey,1)  
  42. end  
  43. close_redis(red)  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值