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,然后同时写入脏数据。


最终解决方法:

  1. 使用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...
}
  1. 将redis版本升级到2.1以上,然后使用


直接在setIfAbsent中设置过期时间

update : 
java 使用redis的事务时不能直接用Api中的multi()和exec(),这样multi()和exec()两次使用的stringRedisTemplate不是一个connect,会导致死锁,正确方式如下:

    private Boolean setLock(RecordEventModel event) {
String lockKey = event.getModel() + ":" + event.getAction() + ":" + event.getId() + ":" + event.getMessage_id();
log.info("lockKey : {}" , lockKey);
SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() {
List<Object> exec = null;
@Override
@SuppressWarnings("unchecked")
public Boolean execute(RedisOperations operations) throws DataAccessException {
operations.multi();
stringRedisTemplate.opsForValue().setIfAbsent(lockKey,JSON.toJSONString(event));
stringRedisTemplate.expire(lockKey,Constants.REDIS_KEY_EXPIRE_SECOND_1_HOUR, TimeUnit.SECONDS);
exec = operations.exec();
if(exec.size() > 0) {
return (Boolean) exec.get(0);
}
return false;
}
};
return stringRedisTemplate.execute(sessionCallback);
}

使用redis事物解决stringRedisTemplate.setIfAbsent()并设置过期时间遇到的问题的更多相关文章

  1. 如何为Redis中list中的项设置过期时间

    问题 两种解决方法 有序集合 多个集合以及TTL Redis是一个伟大的工具,用来在内存中存储列表是很合适的. 不过,如果你想要快速搜索列表,同时需要让列表中每项都在一定时间后过期,应该怎么做呢? 首 ...

  2. Redis原子性写入HASH结构数据并设置过期时间

    Redis中提供了原子性命令SETEX或SET来写入STRING类型数据并设置Key的过期时间: > SET key value EX NX ok > SETEX key value ok ...

  3. redis数据库如何批量删除键和设置过期时间?

    我们可以借助Linux中的xargs,在终端中执行命令来实现这两个功能. 一.批量删除键 批量删除以"key"开头key的方法,需要借助Linux中的xargs,在终端中执行以下命 ...

  4. redis中的key设置过期时间

    EXPIRE key seconds 为给定  key  设置生存时间,当  key  过期时(生存时间为  0  ),它会被自动删除. 在 Redis 中,带有生存时间的  key  被称为『易失的 ...

  5. redis 一二事 - 设置过期时间,以文件夹形式展示key显示缓存数据

    在使用redis时,有时回存在大量数据的时候,而且分类相同,ID相同 可以使用hset来设置,这样有一个大类和一个小分类和一个value组成 但是hset不能设置过期时间 过期时间只能在set上设置 ...

  6. java操作Redis缓存设置过期时间

    关于Redis的概念和应用本文就不再详解了,说一下怎么在java应用中设置过期时间. 在应用中我们会需要使用redis设置过期时间,比如单点登录中我们需要随机生成一个token作为key,将用户的信息 ...

  7. redis批量设置过期时间

    Redis 中有删除单个 Key 的指令 DEL,但好像没有批量删除 Key 的指令,不过我们可以借助 Linux 的 xargs 指令来完成这个动作.代码如下: redis-cli keys &qu ...

  8. redis文档翻译_key设置过期时间

    Available since 1.0.0.    使用開始版本号1.01 Time complexity: O(1)  时间复杂度O(1) 出处:http://blog.csdn.net/colum ...

  9. python中使用redis模块, 设置过期时间

    # 链接数据库self.handle = redis.Redis(host=host, port=port, db=db, password=password, decode_responses=Tr ...

随机推荐

  1. text-align:justify 两端对齐

    今天看页面发现一个以前没用过的css属性text-align:justify,查了一下非常实用,是一个实现文本两端对齐的属性. 使用前: 使用后: 看了一些文章还有结合inline-block+tex ...

  2. [Google] 人和自行车匹配

    2D平面上,有m个人(P),n辆自行车(B),还有空白(O)满足以下条件1.m < n. 2.不存在两个人,到同一辆自行车距离相等, 距离用abs(x1-x2) + abs(y1-y2)定义3. ...

  3. Linux查看CPU和内存使用情况总结

    Linux查看CPU和内存使用情况:http://www.cnblogs.com/xd502djj/archive/2011/03/01/1968041.html 在做Linux系统优化的时候,物理内 ...

  4. Yii2性能优化

    https://www.yiiframework.com/doc/guide/2.0/zh-cn/tutorial-performance-tuning 性能优化 有许多因素影响你的 Web 应用程序 ...

  5. Appium移动自动化测试-----(六)1.appium-desktop下载安装

    Appium 移动测试中有个很重新的组件 Appium-Server,它主要用来监听我们的移动设备(真机或模拟器),然将不同编程语言编写的 appium 测试脚本进行解析,然后,驱动移动设备来运行测试 ...

  6. lua . 命令收集

    io.popen()## 原型:io.popen ([prog [, mode]]) 解释:在额外的进程中启动程序prog,并返回用于prog的文件句柄.通俗的来说就是使用这个函数可以调用一个命令(程 ...

  7. MySQL的索引有哪些

    一.索引是什么 索引,在MySQL中也叫“键(key)”,是存储引擎用于快速找到记录的一种数据结构.如果把数据库的一张表比作一本书,那索引则是这本书的目录,通过目录,我们能快速找到我们想要的主题所对应 ...

  8. poj1056(字符串判断是否存在一个字符串是另一个字符串的前缀)

    题目链接:https://vjudge.net/problem/POJ-1056 题意:给定一个字符串集,判断是否存在一个字符串是另一个字符串的前缀. 思路:和hdoj1671一样,有两种情况: 当前 ...

  9. LeetCode 172. 阶乘后的零(Factorial Trailing Zeroes)

    172. 阶乘后的零 172. Factorial Trailing Zeroes 题目描述 给定一个整数 n,返回 n! 结果尾数中零的数量. LeetCode172. Factorial Trai ...

  10. (一)PlantUML 语法之时序图

    文章目录 前言 `-> -->` 信息传递 声明参与者的几个关键字 `as` 重命名参与者 `#` 添加颜色 `order` 定义参与者顺序 `""` 参与者名字 给自 ...