-
客戶端C2使用
SETNX
命令獲取鎖 -
假設客戶端C1已經崩潰但是仍然持有鎖,所以Redis返回false給客戶端C2
-
客戶端C2使用
GET
命令獲取鎖並檢查鎖是否已經過期,如果沒有過期,則繼續等待一段時間並重新重試 -
如果鎖已經過期,客戶端C2嘗試
GETSET lock.name <current Unix timestamp + lock timeout + 1>
-
利用
GETSET
語法,客戶端C2可以檢查key的舊值(鎖的舊時間)是否仍然是過期時間,如果是,則獲取鎖 -
如果另一個客戶端C3率先獲取到鎖,客戶端C2執行
GETSET
命令后將返回非過期時間,然后客戶端C2繼續從頭開始重新嘗試獲取鎖。此操作客戶端C2將會延長一點客戶端C3獲取到的鎖的過期時間,不過這不是什么大問題。
/** * 獲取一個redis分布鎖 * * @param lockKey 鎖住的key * @param lockExpireMils 鎖住的時長。如果超時未解鎖,視為加鎖線程死亡,其他線程可奪取鎖 * @return */ public boolean lock(String lockKey, long lockExpireMils) { return (Boolean) redisTemplate.execute((RedisCallback) connection -> { long nowTime = System.currentTimeMillis(); Boolean acquire = connection.setNX(lockKey.getBytes(), String.valueOf(nowTime + lockExpireMils + 1).getBytes()); if (acquire) { return Boolean.TRUE; } else { byte[] value = connection.get(lockKey.getBytes()); if (Objects.nonNull(value) && value.length > 0) { long oldTime = Long.parseLong(new String(value)); if (oldTime < nowTime) { //connection.getSet:返回這個key的舊值並設置新值。 byte[] oldValue = connection.getSet(lockKey.getBytes(), String.valueOf(nowTime + lockExpireMils + 1).getBytes()); //當key不存時會返回空,表示key不存在或者已在管道中使用 return oldValue == null ? false : Long.parseLong(new String(oldValue)) < nowTime; } } } return Boolean.FALSE; }); }
參考地址
RedisTemplate用SETNX命令實現分布式鎖 https://www.jianshu.com/p/6dd656e7051f