Redis鎖實現防重複提交和併發問題
阿新 • • 發佈:2020-09-03
@Slf4j @Component public class RedisLock { public static final int LOCK_EXPIRE = 5000; @Autowired private StringRedisTemplate redisTemplate; /** * 分散式鎖 * * @param key key值 * @return 是否獲取到 */ public boolean lock(String key) { String lock = key; try { return (Boolean) redisTemplate.execute((RedisCallback) connection -> { long expireAt = System.currentTimeMillis() + LOCK_EXPIRE; Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes()); if (acquire) { log.info("使用者 [{}]加鎖成功",key); return true; } else { log.info("使用者 [{}]加鎖失敗",key); //判斷該key上的值是否過期了 byte[] value = connection.get(lock.getBytes()); if (Objects.nonNull(value) && value.length > 0) { long expireTime = Long.parseLong(new String(value)); if (expireTime < System.currentTimeMillis()) { // 如果鎖已經過期 byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE).getBytes()); // 防止死鎖 return Long.parseLong(new String(oldValue)) < System.currentTimeMillis(); } } } return false; }); } finally { RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory()); } } /** * 刪除鎖 * * @param key */ public void delete(String key) { try { redisTemplate.delete(key); } finally { RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory()); } }
//實現業務 try{ // 判斷是否獲取了鎖 boolean getLock = redisLock(lockKey); if(getLock){ // 此處可以開始寫需要實現的程式碼 } }catch(Exception e){
e.printStackTrace();
}finally {
// 判斷是否超時了,如果未超時,則釋放鎖。 超時了,鎖有可能被其他執行緒拿走了,就不做任何操作
redisLock.delete(String.valueOf(caronwerId));}
}
知識點: redis Setnx(SETifNot eXists) 命令在指定的 key 不存在時,為 key 設定指定的值。
設定成功,返回 1 。 設定失敗,返回 0 。
redis> EXISTS job # job 不存在 (integer) 0 redis> SETNX job "programmer" # job 設定成功 (integer) 1 redis> SETNX job "code-farmer" # 嘗試覆蓋 job ,失敗 (integer) 0 redis> GET job # 沒有被覆蓋 "programmer"
一.redis命令講解: setnx()命令: setnx的含義就是SET if Not Exists,其主要有兩個引數 setnx(key, value)。 該方法是原子的,如果key不存在,則設定當前key成功,返回1;如果當前key已經存在,則設定當前key失敗,返回0。 get()命令: get(key) 獲取key的值,如果存在,則返回;如果不存在,則返回nil; getset()命令: 這個命令主要有兩個引數 getset(key, newValue)。該方法是原子的,對key設定newValue這個值,並且返回key原來的舊值。 假設key原來是不存在的,那麼多次執行這個命令,會出現下邊的效果: 1. getset(key, “value1”) 返回nil 此時key的值會被設定為value1 2. getset(key, “value2”) 返回value1 此時key的值會被設定為value2 3. 依次類推! 二.具體的使用步驟如下: 1. setnx(lockkey, 當前時間+過期超時時間) ,如果返回1,則獲取鎖成功;如果返回0則沒有獲取到鎖,轉向2。 2. get(lockkey)獲取值oldExpireTime ,並將這個value值與當前的系統時間進行比較,如果小於當前系統時間,則認為這個鎖已經超時,可以允許別的請求重新獲取,轉向3。 3. 計算newExpireTime=當前時間+過期超時時間,然後getset(lockkey, newExpireTime) 會返回當前lockkey的值currentExpireTime。 4. 判斷currentExpireTime與oldExpireTime 是否相等,如果相等,說明當前getset設定成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那麼當前請求可以直接返回失敗,或者繼續重試。 5. 在獲取到鎖之後,當前執行緒可以開始自己的業務處理,當處理完畢後,比較自己的處理時間和對於鎖設定的超時時間,如果小於鎖設定的超時時間,則直接執行delete釋放鎖;如果大於鎖設定的超時時間,則不需要再鎖進行處理