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
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... }
直接在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); }