nginx+lua+redis防刷和限流的实现
作者:laolu0837
本文将介绍如何使用nginx lua redis实现防刷和限流,首先,我们将了解防刷和限流的基本概念和必要性,然后,我们将详细介绍如何使用nginx lua redis实现防刷和限流,感兴趣的可以了解一下
防刷的概念:
防刷的目的是为了防止有些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的代码抄自开涛的博客中相关内容。
防刷代码
-- access_by_lua_file '/opt/ops/lua/access_limit.lua' local function close_redis(red) if not red then return end --释放连接(连接池实现) local pool_max_idle_time = 10000 --毫秒 local pool_size = 100 --连接池大小 local ok, err = red:set_keepalive(pool_max_idle_time, pool_size) if not ok then ngx_log(ngx_ERR, "set redis keepalive error : ", err) end end local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) local ip = "redis-ip" local port = redis-port local ok, err = red:connect(ip,port) if not ok then return close_redis(red) end local clientIP = ngx.req.get_headers()["X-Real-IP"] if clientIP == nil then clientIP = ngx.req.get_headers()["x_forwarded_for"] end if clientIP == nil then clientIP = ngx.var.remote_addr end local incrKey = "user:"..clientIP..":freq" local blockKey = "user:"..clientIP..":block" local is_block,err = red:get(blockKey) -- check if ip is blocked if tonumber(is_block) == 1 then ngx.exit(ngx.HTTP_FORBIDDEN) return close_redis(red) end res, err = red:incr(incrKey) if res == 1 then res, err = red:expire(incrKey,1) end if res > 200 then res, err = red:set(blockKey,1) res, err = red:expire(blockKey,600) end close_redis(red)
限流代码
-- access_by_lua_file '/opt/ops/lua/access_flow_control.lua' local function close_redis(red) if not red then return end --释放连接(连接池实现) local pool_max_idle_time = 10000 --毫秒 local pool_size = 100 --连接池大小 local ok, err = red:set_keepalive(pool_max_idle_time, pool_size) if not ok then ngx_log(ngx_ERR, "set redis keepalive error : ", err) end end local function wait() ngx.sleep(1) end local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) local ip = "redis-ip" local port = redis-port local ok, err = red:connect(ip,port) if not ok then return close_redis(red) end local uri = ngx.var.uri -- 获取当前请求的uri local uriKey = "req:uri:"..uri 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) while (res > 10) do local twait, err = ngx.thread.spawn(wait) ok, threadres = ngx.thread.wait(twait) if not ok then ngx_log(ngx_ERR, "wait sleep error: ", err) break; end 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) end close_redis(red)
到此这篇关于nginx+lua+redis防刷和限流的实现的文章就介绍到这了,更多相关nginx lua redis防刷和限流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!