spring-date-redis 版本:1.6.2 场景:在使用 setIfAbsent(key,value) 时,想对 key 设置一个过期时间,同时需要用到 setIfAbsent 的返回值来指定之后的流程,所以使用了以下代码:
boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value);
if(store){
stringRedisTemplate.expire(key,timeout);
// todo something…
}
这段代码是有问题的:当 setIfAbsent 成功之后断开连接,下面设置过期时间的代码 stringRedisTemplate.expire(key,timeout); 是无法执行的,这时候就会有大量没有过期时间的数据存在数据库。想到一个办法就是添加事务管理,修改后的代码如下:
stringRedisTemplate.setEnableTransactionSupport(true);
stringRedisTemplate.multi();
boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value);
if(store){
stringRedisTemplate.expire(key,timeout);
}
stringRedisTemplate.exec();
if(store){
// todo something…
}
这样就保证了整个流程的一致性。本因为这样就可以了,可是事实总是不尽人意,因为我在文档中发现了以下内容:
加了事务管理之后,setIfAbsent 的返回值竟然是 null,这样就没办法再进行之后的判断了。
好吧,继续解决:
stringRedisTemplate.setEnableTransactionSupport(true);
stringRedisTemplate.multi();
String result = stringRedisTemplate.opsForValue().get(key);
if(StringUtils.isNotBlank(result)){
return false;
}
// 锁的过期时间为 1 小时
stringRedisTemplate.opsForValue().set(key, value,timeout);
stringRedisTemplate.exec();
// todo something…
上边的代码其实还是有问题的,当出现并发时,String result = stringRedisTemplate.opsForValue().get(key); 这里就会有多个线程同时拿到为空的 key,然后同时写入脏数据。
最终解决方法:
使用 stringRedisTemplate.exec(); 的返回值判断 setIfAbsent 是否成功
stringRedisTemplate.setEnableTransactionSupport(true);
stringRedisTemplate.multi();
stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event));
stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS);
List result = stringRedisTemplate.exec(); // 这里 result 会返回事务内每一个操作的结果,如果 setIfAbsent 操作失败后,result[0] 会为 false。
if(true == result[0]){
// todo something…
}
将 redis 版本升级到 2.1 以上,然后使用
直接在 setIfAbsent 中设置过期时间