redis lua实现incr

Table of Contents

redis lua实现倒计时incr   @bolg @original

场景表述

......
网络转摘
访问限制
有时候我们会有一个需求是需要限制一个用户对一个资源的访问频率,我们假定一个用户(用IP作为判断)每分钟对一个资源访问次数不能超过10次。

我们可以使用一个键,每次用户访问则把值加1,当值加到10的时候,我们设定键的过期时间为60秒,并且禁止访问。这时候下次访问发现值为10,
则不让访问了,然后60秒后键被删除,这时候再次创建键。这样就可以解决,但是其实这样时间并不精准,问题还是挺大的。

我们还有一个方案:使用队列。
我们设定一个队列rate.limiting.192.168.1.1(假定是这个IP),我们把每次的访问时间都添加到队列中,当队列长度达到10以后,
判断当前时间与队列第一个值的时间差是否小于60,如果小于60则说明60秒内访问次数超过10次,不允许访问;否则说明可以访问,则把队列头的值删除,队列尾增加当前访问时间。
这种方法可以比较精准的实现访问限制,但是当限制的次数比较大时,这种方法占用的存储空间也会比较大。
......
转载地址:http://irfen.me/redis-learn-10-time-expire-limit-cache/

需求

请求访问在每5分钟(或自定义分钟)内累加到1w进行告警

实现

贴代码

//...
StringBuilder luaScript = new StringBuilder();
/**
 * 通过lua sciprt  设置key incr过期时间,key 在过期时间内进行incr +1
 */
luaScript.append("local key = KEYS[1];\n");
luaScript.append("local ttl = ARGV[1];\n");
luaScript.append("local v = redis.call('get', key);\n");
luaScript.append("local v = tonumber(v);\n");
luaScript.append("if v  == nil then\n");
luaScript.append("v = 1;\n");
luaScript.append("else\n");
luaScript.append("v = v+1;\n");
luaScript.append("end\n");
luaScript.append("local cur_ttl = redis.call('ttl', key);\n");
luaScript.append("if cur_ttl < 0 then\n");
luaScript.append("cur_ttl = ttl;\n");
luaScript.append("end\n");
luaScript.append("local ret = redis.call('set', key, v,'ex', cur_ttl);\n");
luaScript.append("if ret['ok'] == 'OK' then\n");
luaScript.append("return v;\n");
luaScript.append("else\n");
luaScript.append("return nil;\n");
luaScript.append("end\n");
//...

//使用方法
LuaScriptHandler luaScriptHandler = new LuaScriptHandler();
String luaScriptStr = luaScriptHandler.getLuaRedisIncrReset();
List<String> argsSet = new ArrayList<>();
argsSet.add(String.valueOf(this.rediskeyExpire));
List<String> keysSet = new ArrayList<>();
keysSet.add(key);
Object object = jedis.eval(luaScriptStr, keysSet, argsSet);
if(object != null){
return Integer.parseInt( object.toString());
}

备注

脚本的原子性
Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行: 
当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。 
这和使用 MULTI / EXEC 包围的事务很类似。 
在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。
另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。
写一个跑得很快很顺溜的脚本并不难, 因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,
请小心, 因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。

Author: josephzeng (josephzeng36@gmail.com)

Last Updated 2015-12-30. Created by Emacs 24.5.1 (Org mode 8.2.10)

Validate