分布式鎖 - RedLock到底有哪些缺陷?
在上篇中,已經(jīng)說了分布式鎖是個“大坑”,這里就來詳細分析一下,RedLock究竟有哪些缺陷,為啥會引發(fā)Redis作者和另外一位分布式大牛的激烈辯論。
RedLock的基本思路就是為鎖準(zhǔn)備多個副本,避免Redis主從切換的時候,數(shù)據(jù)丟失。
1. RedLock算法詳細解釋
(1)跟ZK一樣,多數(shù)派的思路。假設(shè)部署了5臺Redis,獲取鎖的時候,從5臺機器獲取,只要超過半數(shù)(其中3臺)獲取到鎖,就算獲取鎖成功。允許最多2臺宕機。
(2)鎖必須有一個超時強制釋放機制,假設(shè)10秒。避免客戶端宕機,鎖永遠無法釋放。
(3)取每臺機器的鎖之前,記一下當(dāng)前時間beginTime;取鎖之后,再記一下當(dāng)前時間endTime。
if endTime - beginTime > TTL(10秒),意味著剛獲取到鎖,就已經(jīng)過期了,獲取鎖失敗。
(4)如果獲取鎖失敗,也就是沒有超過半數(shù),則在所有Redis節(jié)點上執(zhí)行釋放鎖操作。(為什么是釋放所有節(jié)點,而不是只釋放成功的那些節(jié)點?還是前面說的網(wǎng)絡(luò)2將軍問題,未成功的那些節(jié)點,可能只是返回客戶端的時候超時了,但實際已經(jīng)加了鎖)
(5)如果第(4)步里面釋放節(jié)點失敗,只能依賴第(2)步里面,鎖的強制釋放機制。
2. 表面看起來,這個算法蠻完美,解決了前面單機版Redis分布式鎖的缺陷,那還存在什么問題呢?
問題1: 宕機重啟之后,2個客戶端拿到同一把鎖。- 延遲重啟
假設(shè)5個節(jié)點是A, B, C, D, E,客戶端1在A, B, C上面拿到鎖,D, E沒有拿到鎖,客戶端1拿鎖成功。 此時,C掛了重啟,C上面鎖的數(shù)據(jù)丟失(假設(shè)機器斷電,數(shù)據(jù)還沒來得及刷盤)??蛻舳?去取鎖,從C, D, E 3個節(jié)點拿到鎖,A, B沒有拿到(還被客戶端1持有),客戶端2也超過多數(shù)派,也會拿到鎖。
為此,Redis作者提出了延遲重啟的辦法:重啟的時候,不立馬重啟,等待TTL時間之后再重啟,保證這臺機器此前參與的那些鎖,全部過期,一筆勾銷。
問題2: 時鐘跳躍
剛上面討論的方案嚴(yán)格依賴時鐘,而5臺機器上面的時鐘是可能有誤差的。
時鐘跳躍的意思就是:實際時間只過了1s鐘(假設(shè)),但系統(tǒng)里面2次時間之差可能是1分鐘,也就是系統(tǒng)之間發(fā)生了跳躍。發(fā)生這種情況,可能是運維人員認(rèn)為修改了系統(tǒng)時間。
時鐘跳躍會產(chǎn)生2個后果:
(1)延遲重啟機制失效。時鐘跳躍可能導(dǎo)致機器掛了立馬重啟,從而出現(xiàn)上面的問題。
(2)時鐘跳躍導(dǎo)致客戶端拿到鎖之后立馬失效。endTime - beginTime 差值太大。這雖然不影響正確性,但影響拿鎖的效率。
那么時鐘回?fù)苣??endTime - beginTime會成為負(fù)值,不影響算法的正確性。
問題3: 客戶端大延遲(比如full GC),2個客戶端拿到同一把鎖。
理論上,一切有超時強制釋放機制的鎖,都可能產(chǎn)生這個問題。服務(wù)端把鎖強制釋放了,但是客戶端的代碼并沒有執(zhí)行完,卡在了某個地方(比如full GC,或者其它原因?qū)е逻M程暫停),這把鎖被分配給了另外一個客戶端。
針對這個問題,Redis又提出了watch dog機制。大致意思就是,鎖快要到期之前,發(fā)現(xiàn)客戶端業(yè)務(wù)邏輯還沒執(zhí)行完,就給鎖續(xù)期,避免鎖被強制釋放,分配給另外一個客戶端。但是,鎖續(xù)期本身是個網(wǎng)絡(luò)操作,也沒辦法保證續(xù)期一定成功!
從這個案例中,可以得到2個重要啟示:
(1)在分布式系統(tǒng)中,嚴(yán)格依賴每臺機器本機時鐘的算法,都可能有風(fēng)險。
(2)一切具有“超時強制釋放機制”的鎖,都可能導(dǎo)致客戶端還在持有鎖的情況下,鎖被強制釋放。
