摘要:集群實(shí)現(xiàn)分布式鎖上面的討論中我們有一個(gè)非常重要的假設(shè)是單點(diǎn)的。但是其實(shí)這已經(jīng)超出了實(shí)現(xiàn)分布式鎖的范圍,單純用沒(méi)有命令來(lái)實(shí)現(xiàn)生成。這個(gè)問(wèn)題用實(shí)現(xiàn)分布式鎖暫時(shí)無(wú)解。結(jié)論并不能實(shí)現(xiàn)嚴(yán)格意義上的分布式鎖。
關(guān)于Redis實(shí)現(xiàn)分布式鎖的問(wèn)題,網(wǎng)絡(luò)上很多,但是很多人的討論基本就是把原來(lái)博主的貼過(guò)來(lái),甚至很多面試官也是一知半解經(jīng)不起推敲就來(lái)面候選人,最近結(jié)合我自己的學(xué)習(xí)和資料查閱,整理一下用Redis實(shí)現(xiàn)分布式鎖的方法,歡迎評(píng)論、交流、討論。
1.單機(jī)Redis實(shí)現(xiàn)分布式鎖 1.1獲取鎖獲取鎖的過(guò)程很簡(jiǎn)單,客戶端向Redis發(fā)送命令:
SET resource_name my_random_value NX PX 30000
my_random_value是由客戶端生成的一個(gè)隨機(jī)字符串,它要保證在足夠長(zhǎng)的一段時(shí)間內(nèi)在所有客戶端的所有獲取鎖的請(qǐng)求中都是唯一的。
NX表示只有當(dāng)resource_name對(duì)應(yīng)的key值不存在的時(shí)候才能SET成功。這保證了只有第一個(gè)請(qǐng)求的客戶端才能獲得鎖,而其它客戶端在鎖被釋放之前都無(wú)法獲得鎖。
PX 30000表示這個(gè)鎖有一個(gè)30秒的自動(dòng)過(guò)期時(shí)間。
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
之前獲取鎖的時(shí)候生成的my_random_value 作為參數(shù)傳到Lua腳本里面,作為:ARGV[1],而 resource_name作為KEYS[1]。Lua腳本可以保證操作的原子性。
1.3 關(guān)于單點(diǎn)Redis實(shí)現(xiàn)分布式鎖的討論網(wǎng)絡(luò)上有文章說(shuō)用如下命令獲取鎖:
SETNX resource_name my_random_value EXPIRE resource_name 30
由于這兩個(gè)命令不是原子的。如果客戶端在執(zhí)行完SETNX后crash了,那么就沒(méi)有機(jī)會(huì)執(zhí)行EXPIRE了,導(dǎo)致它一直持有這個(gè)鎖,其他的客戶端就永遠(yuǎn)獲取不到這個(gè)鎖了。
為什么my_random_value 要設(shè)置成隨機(jī)值?
保證了一個(gè)客戶端釋放的鎖是自己持有的那個(gè)鎖。如若不然,可能出現(xiàn)鎖不安全的情況。
客戶端1獲取鎖成功。 客戶端1在某個(gè)操作上阻塞了很長(zhǎng)時(shí)間。 過(guò)期時(shí)間到了,鎖自動(dòng)釋放了。 客戶端2獲取到了對(duì)應(yīng)同一個(gè)資源的鎖。 客戶端1從阻塞中恢復(fù)過(guò)來(lái),釋放掉了客戶端2持有的鎖。
用 SETNX獲取鎖
網(wǎng)上大量文章說(shuō)用如下命令獲取鎖:
SETNX lock.foo
原文在Redis對(duì)SETNX的官網(wǎng)說(shuō)明,Redis官網(wǎng)文檔建議用Set命令來(lái)代替,主要原因是SETNX不支持超時(shí)時(shí)間的設(shè)置。
https://redis.io/commands/setnx
2.Redis集群實(shí)現(xiàn)分布式鎖上面的討論中我們有一個(gè)非常重要的假設(shè):Redis是單點(diǎn)的。如果Redis是集群模式,我們考慮如下場(chǎng)景:
客戶端1從Master獲取了鎖。 Master宕機(jī)了,存儲(chǔ)鎖的key還沒(méi)有來(lái)得及同步到Slave上。 Slave升級(jí)為Master。 客戶端2從新的Master獲取到了對(duì)應(yīng)同一個(gè)資源的鎖。 客戶端1和客戶端2同時(shí)持有了同一個(gè)資源的鎖,鎖不再具有安全性。
就此問(wèn)題,Redis作者antirez寫(xiě)了RedLock算法來(lái)解決這種問(wèn)題。
2.1 RedLock獲取鎖獲取當(dāng)前時(shí)間。
按順序依次向N個(gè)Redis節(jié)點(diǎn)執(zhí)行獲取鎖的操作。這個(gè)獲取操作跟前面基于單Redis節(jié)點(diǎn)的獲取鎖的過(guò)程相同,包含隨機(jī)字符串my_random_value,也包含過(guò)期時(shí)間(比如PX 30000,即鎖的有效時(shí)間)。為了保證在某個(gè)Redis節(jié)點(diǎn)不可用的時(shí)候算法能夠繼續(xù)運(yùn)行,這個(gè)獲取鎖的操作還有一個(gè)超時(shí)時(shí)間(time out),它要遠(yuǎn)小于鎖的有效時(shí)間(幾十毫秒量級(jí))??蛻舳嗽谙蚰硞€(gè)Redis節(jié)點(diǎn)獲取鎖失敗以后,應(yīng)該立即嘗試下一個(gè)Redis節(jié)點(diǎn)。
計(jì)算整個(gè)獲取鎖的過(guò)程總共消耗了多長(zhǎng)時(shí)間,計(jì)算方法是用當(dāng)前時(shí)間減去第1步記錄的時(shí)間。如果客戶端從大多數(shù)Redis節(jié)點(diǎn)(>= N/2+1)成功獲取到了鎖,并且獲取鎖總共消耗的時(shí)間沒(méi)有超過(guò)鎖的有效時(shí)間(lock validity time),那么這時(shí)客戶端才認(rèn)為最終獲取鎖成功;否則,認(rèn)為最終獲取鎖失敗。
如果最終獲取鎖成功了,那么這個(gè)鎖的有效時(shí)間應(yīng)該重新計(jì)算,它等于最初的鎖的有效時(shí)間減去第3步計(jì)算出來(lái)的獲取鎖消耗的時(shí)間。
如果最終獲取鎖失敗了(可能由于獲取到鎖的Redis節(jié)點(diǎn)個(gè)數(shù)少于N/2+1,或者整個(gè)獲取鎖的過(guò)程消耗的時(shí)間超過(guò)了鎖的最初有效時(shí)間),那么客戶端應(yīng)該立即向所有Redis節(jié)點(diǎn)發(fā)起釋放鎖的操作(即前面介紹的單機(jī)Redis Lua腳本釋放鎖的方法)。
2.2 RedLock釋放鎖客戶端向所有Redis節(jié)點(diǎn)發(fā)起釋放鎖的操作,不管這些節(jié)點(diǎn)當(dāng)時(shí)在獲取鎖的時(shí)候成功與否。
2.3 關(guān)于RedLock的問(wèn)題討論如果有節(jié)點(diǎn)發(fā)生崩潰重啟
假設(shè)一共有5個(gè)Redis節(jié)點(diǎn):A, B, C, D, E。設(shè)想發(fā)生了如下的事件序列:
客戶端1成功鎖住了A, B, C,獲取鎖成功(但D和E沒(méi)有鎖?。?節(jié)點(diǎn)C崩潰重啟了,但客戶端1在C上加的鎖沒(méi)有持久化下來(lái),丟失了。 節(jié)點(diǎn)C重啟后,客戶端2鎖住了C, D, E,獲取鎖成功。 客戶端1和客戶端2同時(shí)獲得了鎖。
為了應(yīng)對(duì)這一問(wèn)題,antirez又提出了延遲重啟(delayed restarts)的概念。也就是說(shuō),一個(gè)節(jié)點(diǎn)崩潰后,先不立即重啟它,而是等待一段時(shí)間再重啟,這段時(shí)間應(yīng)該大于鎖的有效時(shí)間(lock validity time)。這樣的話,這個(gè)節(jié)點(diǎn)在重啟前所參與的鎖都會(huì)過(guò)期,它在重啟后就不會(huì)對(duì)現(xiàn)有的鎖造成影響。
如果客戶端長(zhǎng)期阻塞導(dǎo)致鎖過(guò)期
解釋一下這個(gè)時(shí)序圖,客戶端1在獲得鎖之后發(fā)生了很長(zhǎng)時(shí)間的GC pause,在此期間,它獲得的鎖過(guò)期了,而客戶端2獲得了鎖。當(dāng)客戶端1從GC pause中恢復(fù)過(guò)來(lái)的時(shí)候,它不知道自己持有的鎖已經(jīng)過(guò)期了,它依然向共享資源(上圖中是一個(gè)存儲(chǔ)服務(wù))發(fā)起了寫(xiě)數(shù)據(jù)請(qǐng)求,而這時(shí)鎖實(shí)際上被客戶端2持有,因此兩個(gè)客戶端的寫(xiě)請(qǐng)求就有可能沖突(鎖的互斥作用失效了)。
如何解決這個(gè)問(wèn)題呢?引入了fencing token的概念:
客戶端1先獲取到的鎖,因此有一個(gè)較小的fencing token,等于33,而客戶端2后獲取到的鎖,有一個(gè)較大的fencing token,等于34??蛻舳?從GC pause中恢復(fù)過(guò)來(lái)之后,依然是向存儲(chǔ)服務(wù)發(fā)送訪問(wèn)請(qǐng)求,但是帶了fencing token = 33。存儲(chǔ)服務(wù)發(fā)現(xiàn)它之前已經(jīng)處理過(guò)34的請(qǐng)求,所以會(huì)拒絕掉這次33的請(qǐng)求。這樣就避免了沖突。
但是其實(shí)這已經(jīng)超出了Redis實(shí)現(xiàn)分布式鎖的范圍,單純用Redis沒(méi)有命令來(lái)實(shí)現(xiàn)生成Token。
時(shí)鐘跳躍問(wèn)題
假設(shè)有5個(gè)Redis節(jié)點(diǎn)A, B, C, D, E。
客戶端1從Redis節(jié)點(diǎn)A, B, C成功獲取了鎖(多數(shù)節(jié)點(diǎn))。由于網(wǎng)絡(luò)問(wèn)題,與D和E通信失敗。 節(jié)點(diǎn)C上的時(shí)鐘發(fā)生了向前跳躍,導(dǎo)致它上面維護(hù)的鎖快速過(guò)期。 客戶端2從Redis節(jié)點(diǎn)C, D, E成功獲取了同一個(gè)資源的鎖(多數(shù)節(jié)點(diǎn))。 客戶端1和客戶端2現(xiàn)在都認(rèn)為自己持有了鎖。 這個(gè)問(wèn)題用Redis實(shí)現(xiàn)分布式鎖暫時(shí)無(wú)解。而生產(chǎn)環(huán)境這種情況是存在的。
結(jié)論
Redis并不能實(shí)現(xiàn)嚴(yán)格意義上的分布式鎖。但是這并不意味著上面討論的方案一無(wú)是處。如果你的應(yīng)用場(chǎng)景為了效率(efficiency),協(xié)調(diào)各個(gè)客戶端避免做重復(fù)的工作,即使鎖失效了,只是可能把某些操作多做一遍而已,不會(huì)產(chǎn)生其它的不良后果。但是如果你的應(yīng)用場(chǎng)景是為了正確性(correctness),那么用Redis實(shí)現(xiàn)分布式鎖并不合適,會(huì)存在各種各樣的問(wèn)題,且解決起來(lái)就很復(fù)雜,為了正確性,需要使用zab、raft共識(shí)算法,或者使用帶有事務(wù)的數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)嚴(yán)格意義上的分布式鎖。
參考資料
Distributed locks with Redis
基于Redis的分布式鎖到底安全嗎(上)? - 鐵蕾的個(gè)人博客
https://martin.kleppmann.com/...
技術(shù)文章匯總
【Leetcode】101. 對(duì)稱二叉樹(shù)
【Leetcode】100. 相同的樹(shù)
【Leetcode】98. 驗(yàn)證二叉搜索樹(shù)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/11998.html
摘要:三年百度,五年阿里,阿里架構(gòu)師淺談我是如何順利進(jìn)入前些天在我群里認(rèn)識(shí)了以為挺有意思的老哥,他也是工作年多技術(shù)和面試都不差,最近也是在找工作,是從京城來(lái)魔都的,也和他撈了不少。 說(shuō)來(lái)慚愧,也不怕你們笑話。做開(kāi)發(fā)8年多,到目前還是一名不折不扣的掃地僧。年前的辭職,到現(xiàn)在還在家靜養(yǎng)中。其實(shí)也沒(méi)什么,就是回家總結(jié)一下自己這些年來(lái)在外工作與面試等做一個(gè)簡(jiǎn)單的總結(jié)與反思。做一下自己后面一個(gè)人生規(guī)劃...
摘要:作為面試官,我是如何甄別應(yīng)聘者的包裝程度語(yǔ)言和等其他語(yǔ)言的對(duì)比分析和主從復(fù)制的原理詳解和持久化的原理是什么面試中經(jīng)常被問(wèn)到的持久化與恢復(fù)實(shí)現(xiàn)故障恢復(fù)自動(dòng)化詳解哨兵技術(shù)查漏補(bǔ)缺最易錯(cuò)過(guò)的技術(shù)要點(diǎn)大掃盲意外宕機(jī)不難解決,但你真的懂?dāng)?shù)據(jù)恢復(fù)嗎每秒 作為面試官,我是如何甄別應(yīng)聘者的包裝程度Go語(yǔ)言和Java、python等其他語(yǔ)言的對(duì)比分析 Redis和MySQL Redis:主從復(fù)制的原理詳...
閱讀 1083·2021-11-16 11:45
閱讀 3085·2021-10-13 09:40
閱讀 696·2019-08-26 13:45
閱讀 1157·2019-08-26 13:32
閱讀 2145·2019-08-26 13:23
閱讀 880·2019-08-26 12:16
閱讀 2807·2019-08-26 11:37
閱讀 1734·2019-08-26 10:32