分散式鎖-Redis
分散式鎖的實現有哪些?
1.Memcached分散式鎖
利用Memcached的add命令。此命令是原子性操作,只有在key不存在的情況下,才能add成功,也就意味著執行緒得到了鎖。
2.Redis分散式鎖
和Memcached的方式類似,利用Redis的setnx命令。此命令同樣是原子性操作,只有在key不存在的情況下,才能set成功。(setnx命令並不完善,後續會介紹替代方案)
3.Zookeeper分散式鎖
利用Zookeeper的順序臨時節點
,來實現分散式鎖和等待佇列。Zookeeper設計的初衷,就是為了實現分散式鎖服務的。
4.Chubby
Google公司實現的粗粒度分散式鎖服務,底層利用了Paxos一致性演算法。
如何用Redis實現分散式鎖?
分散式鎖實現的三個核心要素:
1.加鎖
最簡單的方法是使用setnx命令。key是鎖的唯一標識,按業務來決定命名。比如想要給一種商品的秒殺活動加鎖,可以給key命名為 “lock_sale_商品ID” 。而value設定成什麼呢?我們可以姑且設定成1。加鎖的虛擬碼如下:
setnx(key,1)
當一個執行緒執行setnx返回1,說明key原本不存在,該執行緒成功得到了鎖;當一個執行緒執行setnx返回0,說明key已經存在,該執行緒搶鎖失敗。
2.解鎖
有加鎖就得有解鎖。當得到鎖的執行緒執行完任務,需要釋放鎖,以便其他執行緒可以進入。釋放鎖的最簡單方式是執行del指令,虛擬碼如下:
del(key)
釋放鎖之後,其他執行緒就可以繼續執行setnx命令來獲得鎖。
3.鎖超時
鎖超時是什麼意思呢?如果一個得到鎖的執行緒在執行任務的過程中掛掉,來不及顯式地釋放鎖,這塊資源將會永遠被鎖住,別的執行緒再也別想進來。
所以,setnx的key 必須設定一個超時時間,以保證即使沒有被顯式釋放,這把鎖也要在一定時間後自動釋放。setnx不支援超時引數,所以需要額外的指令,虛擬碼如下:
expire(key, 30)
綜合起來,我們分散式鎖實現的第一版虛擬碼如下:
if(setnx(key,1) == 1){
expire(key,30)
try {
do something ......
} finally {
del(key)
}
}
上面的虛擬碼中,存在著
三個致命問題:
1. setnx和expire的非原子性
設想一個極端場景,當某執行緒執行setnx,成功得到了鎖:
setnx剛執行成功,還未來得及執行expire指令,節點1 掛掉了。
這樣一來,這把鎖就沒有設定過期時間,變得“長生不老”,別的執行緒再也無法獲得鎖了。怎麼解決呢?
setnx指令本身是不支援傳入超時時間的,Redis 2.6.12以上版本為set指令增加了可選引數,虛擬碼如下:
set(key,1[value],30[超時時間],NX)
這樣就可以取代setnx指令。
SET user_key user_value NX PX 100
NX:只在在鍵不存在時,才對鍵進行設定操作,SET key value NX 效果等同於 SETNX key value
PX millisecond:設定鍵的過期時間為millisecond毫秒,當超過這個時間後,設定的鍵會自動失效
當redis中不存在user_key這個鍵的時候,才會去設定一個user_key鍵,並且給這個鍵的值設定為 user_value,且這個鍵的存活時間為100ms
只有在某個key不存在的時候,才會執行成功。那麼當多個程式同時併發的去設定同一個key的時候,就永遠只會有一個程式成功。
2. del 導致誤刪
極端場景,假如某執行緒(A)成功得到了鎖,並且設定的超時時間是30秒。
如果某些原因導致執行緒A執行的很慢很慢,過了30秒都沒執行完,這時候鎖過期自動釋放,執行緒B得到了鎖。
隨後,執行緒A執行完了任務,執行緒A接著執行del指令來釋放鎖。但這時候執行緒B還沒執行完,執行緒A實際上刪除的是執行緒B加的鎖。
在del釋放鎖之前需判斷,驗證當前的鎖是不是自己加的鎖。
具體的實現,在刪除之前驗證key對應的value是不是自己當初設定的那個。
加鎖:
String threadId = Thread.currentThread().getId()
set(key,threadId ,30,NX)//把當前的執行緒ID當做value
解鎖:
if(threadId .equals(redisClient.get(key))){
del(key)
}
但是,這樣做又隱含了一個新的問題,判斷和釋放鎖是兩個獨立操作,不是原子性。
我們都是追求極致的程式設計師,所以這一塊要用 Lua指令碼
來實現:
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));
這樣一來,驗證和刪除過程就是原子操作了。
3. 出現併發的可能性
還是剛才第二點所描述的場景,雖然我們避免了執行緒A誤刪掉key的情況,但是同一時間有A,B兩個執行緒在訪問程式碼塊,仍然是不完美的。
怎麼辦呢?我們可以讓獲得鎖的執行緒開啟一個守護執行緒,用來給快要過期的鎖“續航”。
set 了30秒,當過去了29秒,執行緒A還沒執行完,這時候守護執行緒會執行expire指令,為這把鎖“續命”20秒。守護執行緒從第29秒開始執行,每20秒執行一次。
當執行緒A執行完任務,會顯式關掉守護執行緒。
另一種情況,如果節點1 忽然斷電,由於執行緒A和守護執行緒在同一個程式,守護執行緒也會停下。這把鎖到了超時的時候,沒人給它續命,也就自動釋放了。
相關文章
- 十九、Redis分散式鎖、Zookeeper分散式鎖Redis分散式
- Redis分散式鎖Redis分散式
- Redis 分散式鎖Redis分散式
- Redis分散式鎖解析Redis分散式
- redis系列:分散式鎖Redis分散式
- Springboot + redis分散式鎖Spring BootRedis分散式
- Redis 分散式鎖(一)Redis分散式
- Redis分散式鎖加鎖案例Redis分散式
- redis分散式鎖-可重入鎖Redis分散式
- 分散式鎖----Redis實現分散式Redis
- 細說Redis分散式鎖?Redis分散式
- Redis分散式鎖實戰Redis分散式
- Redis 應用-分散式鎖Redis分散式
- Redis實現分散式鎖Redis分散式
- 【180414】分散式鎖(redis/mysql)分散式RedisMySql
- 基於 Redis 分散式鎖Redis分散式
- 詳解Redis分散式鎖Redis分散式
- 【Redis】利用 Redis 實現分散式鎖Redis分散式
- 分散式鎖之Redis實現分散式Redis
- 利用Redis實現分散式鎖Redis分散式
- 分散式鎖--Redis小試牛刀分散式Redis
- Redis 分散式鎖解決方案Redis分散式
- redis分散式鎖-SETNX實現Redis分散式
- 基於redis的分散式鎖Redis分散式
- 分散式鎖實現(一):Redis分散式Redis
- Redis分散式鎖解決方案Redis分散式
- 基於redis做分散式鎖Redis分散式
- Redis之分散式鎖實現Redis分散式
- Redis-10-分散式鎖.mdRedis分散式
- 基於 Redis 的分散式鎖Redis分散式
- Redis如何實現分散式鎖Redis分散式
- redis分散式鎖的實現Redis分散式
- redis分散式鎖-java實現Redis分散式Java
- 關於分散式鎖原理的一些學習與思考-redis分散式鎖,zookeeper分散式鎖分散式Redis
- Redis面試系列:Redis實現分散式鎖Redis面試分散式
- 基於redis實現分散式鎖Redis分散式
- Redis優雅實現分散式鎖Redis分散式
- 實現一個 Redis 分散式鎖Redis分散式