您的位置:首页 > 数据库 > Redis

nginx+lua+redis防刷,lua代码

2017-12-19 14:18 134 查看
问题:

当机器过多时,在每台机器的nginx上用nginx自带防刷模块,往往限制太松。

思路:

多台机器,通过nginx-lua模块,连接redis,以ip(记得nginx安装real-ip模块,取到x-forword-for字段对应的真实用户ip)来更新访问次数,并根据redis设置的阀值进行比较,决定是否限流,不考虑并发更新丢值情况,因为访问次数足够时,总能到达阀值。

优化:

1、redis发送请求,需要查访问次数和阀值,发两次请求浪费资源,可进行合并。

通过eval指令,执行redis-lua脚本,一次性返回两个字段。

2、每次发送redis执行的lua脚本,传输字符较多,浪费带宽。

通过evalsha执行脚本对应的校验码。若redis执行过一个lua脚本后,会记录脚本,并生成对应的校验码,可通过evalsha指令,参数为对应的校验码,即可执行脚本。

3、(不采用)建立nginx到redis的连接池,防止每次都建立链接。但考虑到以下两点,所以放弃使用,如有错误和方案,麻烦指正。

(1)、连接池不应该很大,所以在并发量很大的时候,大多数请求还是要从新建立连接。

(2)、要维护连接池,保证同一时刻没有两个请求共用一个链接,造成连接关闭的异常。没有一个变量,用来标识调用不同的连接。

lua代码(可直接使用,亲测可用):

首先需要在nginx上输入下面的配置:

lua_shared_dict ngx_shared_redis 1m;

建立一个从nginx启动后便存在的共享变量,1m大小(可以小点),名字叫redis。用于存储生成的redis-lua叫的校验码,用ngx.shared.redis:get("redis")方式来获取。

--关闭连接
local function close_redis(red)
if not re
4000
d then
return
end
red:close()
end
--主要处理函数
local function get_limit()
--加载模块
local red = require("resty.redis"):new()
red:set_timeout(100)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "redis_connect:"..err)
return close_redis(red)
end
   --根据ip作为key,来防刷
local key = ngx.var.remote_addr
--判断之前是否已经执行过redis-lua脚本,且有对应的校验码值用于调用
if not ngx.shared.redis:get("redis") then
--若之前未执行过,第一次执行
local script = table.concat({
--手动拼接为一行,两个参数,第一个为key即ip,第二个为限制阀值key
"local val = redis.call('get',KEYS[1]) ",
"if val then ",
"val = redis.call('incr',KEYS[1]) ",
"return {val,redis.call('get', KEYS[2])} ",
"end ",
"redis.call('set',KEYS[1],1) ",
"redis.call('expire',KEYS[1],60) ",
"return {0,nil}"
})
--在redis加载对应的lua脚本
local sha1, err = red:script("load", script)
if not sha1 then
ngx.log(ngx.ERR, "load_script:"..err)
return close_redis(red)
end
--拿到生成的校验码,更新对应的字段
ngx.shared.redis:set("redis", sha1)
end
--根据校验码,执行脚本,cart_limit为限制阀值key
local resp, err = red:evalsha(ngx.shared.redis:get("redis"), 2, key, "cart_limit");
if not resp then
--若执行失败,一种情况为,redis清空了脚本缓存,此情况下,退出并删除存储的校验码,等下次执行,再更新
ngx.log(ngx.ERR, "not_resp:"..err)
ngx.shared.redis:delete("redis")
return close_redis(red)
end
--限制值若redis没有,默认为1200,若有则判断是否大于120
local limit = 1200
if resp[2] then
local new_limit = tonumber(resp[2])
limit = new_limit > 120 and new_limit or 1200
end
--若次数超过限制,则拦截
if resp[1] > limit then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
close_redis(red)
end
--用pcall,调用脚本,类似try-catch,如有错误,打印脚本
if not pcall(get_limit) then
ngx.log(ngx.ERR, "lua error")
end


以上若有错误,欢迎指正,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  nginx lua redis 限流 防刷