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")方式来获取。
以上若有错误,欢迎指正,谢谢!
当机器过多时,在每台机器的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
以上若有错误,欢迎指正,谢谢!
相关文章推荐
- lua同c++的交互之在c++中调用lua代码
- 使用VS插件在VS2012/2013上编辑和调试Quick-Cocos2d-x的Lua代码
- 调试lua代码小结
- android中使用lua来写代码
- cocos2dx 3.10 lua环境配置与代码调试
- ToLua LuaFramework 使用实战[1]-代码热更新
- Lua操作字符串的5个代码片段分享
- openresty 应用打包并使用luajit编译lua代码实现简单加密
- 常用lua代码段
- 干货|使用luacov统计lua代码覆盖率
- 编写高效Lua代码的方法(整理)
- cocos2d-x+lua代码热加载(Hot Swap)的研究
- Lua1.0 代码分析 hash.c
- 在线调试 Lua 代码
- Cocos 2d-X Lua 游戏添加苹果内购(二) OC和Lua交互代码详解
- Lua中的类编程代码实例
- 编写高性能的Lua代码
- [转]lua元表代码分析
- Lua中遍历文件操作代码实例
- 简单运行Lua代码