Redis通过getset和incr实现复位操作计数器

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis通过getset和incr实现复位操作计数器相关的知识,希望对你有一定的参考价值。

参考技术A

举例来说,每次当某个事件发生时,进程可能对一个名为 key调用 [ INCR ] 操作,通常我们还要在一个原子时间内同时完成获得计数器的值和将计数器值复位为 0 两个操作。

可以用命令 GETSET mycounter 0 来实现这一目标。

Redis通过Lua实现原子操作

工作中我们经常利用 redis 来实现限速, 比如限制一个手机号 60秒最多发送3条短信.

如果不考虑原子性, 伪代码如下:

long count = incr('手机号') ; 
if count==0  expire('手机号',60) ; 
if count>3   return "发送频率超限" ; 

上面代码在执行时, 前后可能调用 redis 两次, 网络耗时的问题,忽略不说, 但另外一个问题应该被重视, 就是整个执行的原子性, 假如 incr 执行成功,而 expire 执行失败, 那么 reids 里面就会存在不主动失效的脏数据.

为了保证整个执行的原子性, 我们来利用 Lua.

首先把 incr 和 expire 的调用逻辑一起封装在 Lua 里, 然后再通过 redis 调用.

定义脚本文件 rate_limit.lua ,内容如下:

-- incr
local count = redis.call('incr',KEYS[1])

-- 第一次时,设置expire
if count == 1 then
  redis.call('expire',KEYS[1],ARGV[1])
end

-- 返回目前次数
return count

通过 redis-cli 调用 lua 测试:

redis-cli --eval rate_limit.lua 手机号 , 60
(integer) 1
redis-cli --eval rate_limit.lua 手机号 , 60
(integer) 2

如果项目是 Java 编写,且使用了 spring-data-redis, 可参考代码如下:

String lua = "";
lua += "local count = redis.call('incr',KEYS[1]) ";
lua += "if count == 1 then ";
lua += "redis.call('expire',KEYS[1],ARGV[1]) ";
lua += "end ";
lua += "return count";
RedisScript redisScript=new DefaultRedisScript(lua,Long.TYPE);
Long count=(Long)redisTemplate.execute(redisScript, Arrays.asList("手机号"), 60);
if(count>3){
   return "发送频率超限";



以上是关于Redis通过getset和incr实现复位操作计数器的主要内容,如果未能解决你的问题,请参考以下文章

REDIS自增INCR设置过期时间的原子操作(LUA&PHP实现)

Redis过期时间

Jedis操作Redis--String类型

Jedis操作Redis--String类型

基于Redis的INCR实现一个限流器

redis 实现锁