————————————
分布式鎖有哪些實(shí)現(xiàn)?
1.Memcached分布式鎖
使用Memcached的add命令。這個(gè)命令是一個(gè)原子操作。只有當(dāng)密鑰不存在時(shí),添加才能成功,這意味著線程被鎖定。
2.Redis分布式鎖
與Memcached類似,使用Redis的setnx命令。這個(gè)命令也是原子操作。只有當(dāng)密鑰不存在時(shí),集合才能成功。(setnx命令并不完美,稍后將介紹替代命令。)
3.動(dòng)物園管理員分布式鎖
利用動(dòng)物園管理員的順序臨時(shí)節(jié)點(diǎn),實(shí)現(xiàn)了分布式鎖和等待隊(duì)列。Zookeeper最初是為了實(shí)現(xiàn)分布式鎖服務(wù)而設(shè)計(jì)的。
4.圓胖的
Google的粗粒度分布式鎖服務(wù),底層使用Paxos一致性算法。
如何用Redis實(shí)現(xiàn)分布式鎖?
Redis分布式鎖的基本流程并不難理解,但是要寫得完美卻不是那么容易。在這里,我們需要理解分布式鎖實(shí)現(xiàn)的三個(gè)核心元素:
1.鎖
最簡(jiǎn)單的方法是使用setnx命令。鑰匙是鎖的唯一標(biāo)識(shí)符,根據(jù)業(yè)務(wù)命名。比如你想鎖定一個(gè)商品的尖峰活動(dòng),可以把鑰匙命名為“l(fā)ock_sale_ commodity ID”。值設(shè)置為什么?就設(shè)成1吧。鎖定的偽代碼如下:
setnx(鍵,1)
當(dāng)一個(gè)線程執(zhí)行setnx并返回1時(shí),表示密鑰原本不存在,線程成功獲得鎖;當(dāng)一個(gè)線程執(zhí)行setnx并返回0時(shí),表示key已經(jīng)存在,線程搶鎖失敗。
2.用鑰匙開鎖
有鎖就要開鎖。當(dāng)被鎖定的線程完成任務(wù)時(shí),它需要釋放鎖,以便其他線程可以進(jìn)入。釋放鎖最簡(jiǎn)單的方法是執(zhí)行del指令,偽代碼如下:
del(鍵)
釋放鎖后,其他線程可以繼續(xù)執(zhí)行setnx命令來獲取鎖。
3.鎖定超時(shí)
鎖超時(shí)是什么意思?如果一個(gè)被鎖定的線程在執(zhí)行一個(gè)任務(wù)的過程中掛起,并且顯式釋放鎖已經(jīng)太晚了,那么這個(gè)資源將永遠(yuǎn)被鎖定,其他線程也永遠(yuǎn)不會(huì)再進(jìn)來。
所以setnx的key必須設(shè)置一個(gè)超時(shí),以保證鎖在一定時(shí)間后即使沒有明確釋放也會(huì)自動(dòng)釋放。Setnx不支持超時(shí)參數(shù),所以需要額外的指令。偽代碼如下:
過期(密鑰,30)
總而言之,我們的分布式鎖實(shí)現(xiàn)的第一個(gè)偽代碼如下:
if(setnx(key,1)= 1){
過期(密鑰,30)
嘗試{
做某事......
}最后{
del(鍵)
}
}
代碼結(jié)尾不錯(cuò),怎么回家等通知?
因?yàn)樯厦娴膫未a有三個(gè)致命的問題:
1.集合NX和過期的非原子性
想象一個(gè)極端的場(chǎng)景,當(dāng)一個(gè)線程執(zhí)行setnx時(shí),它成功地獲得了鎖:
Setnx剛剛成功執(zhí)行,但是在它能夠執(zhí)行expire命令之前,節(jié)點(diǎn)1 Duang掛起。
這樣,鎖沒有設(shè)置到期時(shí)間,變成“不死”,其他線程就無法再獲得鎖。
怎么解決?Setnx指令本身不支持傳入超時(shí)。幸運(yùn)的是,REDIS 2 . 6 . 12版或以上版本給set指令增加了可選參數(shù),偽代碼如下:
設(shè)置(鍵,1,30,NX)
這樣就可以替換setnx命令了。
2.del導(dǎo)致錯(cuò)誤刪除
另一個(gè)極端的情況是,如果一個(gè)線程成功獲得鎖,并且設(shè)置的超時(shí)時(shí)間是30秒。
如果線程A由于某種原因執(zhí)行緩慢,30秒后沒有完成,那么鎖過期自動(dòng)釋放,線程B得到鎖。
然后,線程a完成執(zhí)行任務(wù),然后線程a執(zhí)行del指令釋放鎖。但此時(shí)線程B還沒有執(zhí)行完,線程A實(shí)際上刪除了線程B添加的鎖。
如何避免這種情況?您可以在del釋放鎖之前進(jìn)行判斷,以驗(yàn)證當(dāng)前鎖是否是自添加的。
至于具體實(shí)現(xiàn),可以在鎖定時(shí)以當(dāng)前線程ID為值,在刪除前驗(yàn)證key對(duì)應(yīng)的值是否是自己線程的ID。
鎖定:
string threadId = thread . Currentthread()。getId()
set(key,threadId,30,NX)
解鎖:
if(ThreadId . equals(RedIsclient . get(key))){
del(鍵)
}
然而,這意味著一個(gè)新的問題:判斷和釋放鎖是兩個(gè)獨(dú)立的操作,而不是原子性。
我們都是追求完美的程序員,所以這一塊應(yīng)該用Lua腳本來實(shí)現(xiàn):
string LuAscript = " if redis . call(' get ',KEYS[1]) == ARGV[1]然后returnredis.call('del ',KEYS[1])else return 0 end ";
redisClient.eval(lua,Collections.singletonList(key),collections . singletonlist(threadId));
這樣,驗(yàn)證和刪除過程就是一個(gè)原子操作。
3.并發(fā)的可能性
在剛才第二點(diǎn)描述的場(chǎng)景中,雖然我們避免了線程A誤刪鍵的情況,但是有兩個(gè)線程A和B同時(shí)訪問代碼塊,這還是不完善的。
我該怎么辦?我們可以讓獲得鎖的線程打開一個(gè)守護(hù)線程來“延長(zhǎng)”即將到期的鎖。
29秒后,線程A還沒有執(zhí)行完。此時(shí),守護(hù)線程將執(zhí)行expire指令來“更新”鎖20秒。守護(hù)線程從第29秒開始執(zhí)行,每20秒執(zhí)行一次。
當(dāng)線程a完成任務(wù)時(shí),它將顯式關(guān)閉守護(hù)線程。
另一方面,如果節(jié)點(diǎn)1突然斷電,守護(hù)線程將停止,因?yàn)榫€程A和守護(hù)線程在同一個(gè)進(jìn)程中。鎖超時(shí)沒人續(xù),自動(dòng)解除。
守護(hù)線程的代碼不難實(shí)現(xiàn)。有了大致的思路,你可以嘗試自己去實(shí)現(xiàn)。
ID:zxsycjh
1.《setnx 漫畫:什么是分布式鎖?》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識(shí),僅代表作者本人觀點(diǎn),與本網(wǎng)站無關(guān),侵刪請(qǐng)聯(lián)系頁(yè)腳下方聯(lián)系方式。
2.《setnx 漫畫:什么是分布式鎖?》僅供讀者參考,本網(wǎng)站未對(duì)該內(nèi)容進(jìn)行證實(shí),對(duì)其原創(chuàng)性、真實(shí)性、完整性、及時(shí)性不作任何保證。
3.文章轉(zhuǎn)載時(shí)請(qǐng)保留本站內(nèi)容來源地址,http://f99ss.com/caijing/1014846.html