使用redis事物解决stringRedisTemplate.setIfAbsent()并设置过时时间遇到的问题

spring-date-redis版本:1.6.2
场景:在使用setIfAbsent(key,value)时,想对key设置一个过时时间,同时须要用到setIfAbsent的返回值来指定以后的流程,因此使用了如下代码:java

boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value);
if(store){
  stringRedisTemplate.expire(key,timeout); 
  // todo something...  
}

这段代码是有问题的:当setIfAbsent成功以后断开链接,下面设置过时时间的代码 stringRedisTemplate.expire(key,timeout); 是没法执行的,这时候就会有大量没有过时时间的数据存在数据库。想到一个办法就是添加事务管理,修改后的代码以下:redis

stringRedisTemplate.setEnableTransactionSupport(true);
stringRedisTemplate.multi();
boolean store = stringRedisTemplate.opsForValue().setIfAbsent(key,value);
if(store){
  stringRedisTemplate.expire(key,timeout);   
}
stringRedisTemplate.exec();
if(store){
    // todo something...
}

这样就保证了整个流程的一致性。本由于这样就能够了,但是事实老是不尽人意,由于我在文档中发现了如下内容:
图片描述spring

加了事务管理以后,setIfAbsent的返回值居然是null,这样就没办法再进行以后的判断了。数据库

好吧,继续解决:session

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


最终解决方法:ide

  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中设置过时时间spa

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);
    }
相关文章
相关标签/搜索