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); }