记录一下Lua脚本原子性操作Redis
命令示例
EVAL "local result = redis.call('GET', KEYS[1] ) if result == false then redis.call('SET', KEYS[1] , 'ok') redis.call('EXPIRE', KEYS[1], ARGV[1]) return 1 else return 0 end" 1 lock 60
参数解析
- 1 : 偏移量 代表从其后开始 参数列表多少个是KEYS的数据,剩下的均为ARGV的数据
- KEYS : 通常用来 传键
- ARGV : 通常用来 传值
Java示例
// 莫慌注入的实际上是一个工厂对象 熟悉的 ThreadLocal 套路
@Resource
HttpServletRequest httpServletRequest;
@Resource
RedisTemplate redisTemplate;
@Around("@annotation(limit)")
public Object limit(ProceedingJoinPoint point, Limit limit) throws Throwable {
User user = getUser(httpServletRequest);
// lua 保证查询与赋值逻辑原子性 防止插队
String key = "limit:" + getMd5(point.getSignature().toString()) + ":" + user.getUserId();
String luaScript = "local result = redis.call('GET', KEYS[1] ) if result == false then redis.call('SET', KEYS[1] , 1) redis.call('EXPIRE', KEYS[1], ARGV[1]) return 1 else return 0 end";
RedisScript<Long > redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), limitSubmit.time());
if (Objects.equals(result, 0L)) {
throw new RuntimeException("OGM 慢点,慢点 要遭不住了!!!");
}
return point.proceed();
}
lua 逻辑 有则返回 0 不存在 则保存 并 设置过期时间 将查询 和 保存 通过 lua 原子化 注意 lua 返回的数字要用 long 接收哦 另外 lua 只能保证批量执行 没人插队 报错了可回滚不了哦