Redis分散式鎖(Redlock官方文件的理解)

莊傑森發表於2019-04-27

Redis分散式鎖(Redlock官方文件的理解)

我github部落格原文

官網解釋

分散式鎖在許多不同程式下需要對共享資源進行互斥操作的環境下,十分需要

Redis作者提出了 Redlock 演算法

開始介紹:

安全和活躍度(可靠性)保障(Safety and Liveness)

需要設計合理分散式鎖,並滿足基本的保障
1.安全 -> 互斥屬性,在任何條件下,只允許一個客戶端拿到鎖
2.活躍度(可靠性)A -> 死鎖檢測,即使有獲取到鎖的客戶端崩潰或者不可用,但是最終鎖還是能被獲取到
3.活躍度(可靠性)B -> 容錯,只要大部分的redis節點都存活,客戶端就能夠獲取鎖和釋放鎖
複製程式碼

為什麼故障恢復的實現還不足夠

有個Master + slave
1.客戶端A獲取鎖
2.master 在命令傳播給slave前就崩潰了
3.slave這時候還不存在key,所以當它被晉升成master時
4.客戶端B嘗試獲取鎖,就被獲取到了-> 這時候就不滿足redis分散式鎖的安全性了

在極端情況下,叢集服務發生失敗時,多客戶端可能同時獲取鎖
複製程式碼

單例項的正確實現

在解決上述問題前,先把基本的redis分散式鎖的設計做好

當我們獲取鎖的時候,執行下面命令列:

SET resource_name my_random_value NX PX 30000
NX表示當key不存在才能設定成功
PX表示超時時間 
my_random_value 需要在所有客戶端和獲取鎖的請求中表示唯一
複製程式碼

釋放鎖,需要帶著 my_random_value 作標識然後再del,2個操作作原子,所以推薦使用lua指令碼

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
複製程式碼

說明

1.這樣就能有效的阻止其他客戶端生成鎖。(應該不用多說明吧。)
2.my_random_value 應該怎麼設定,反正容量越小,消耗越小越好,文件上給出是使用clientId加上時間戳或者使用RC4演算法
3.expire的時間設定,我們稱作鎖有效時間,是鎖自動釋放時間 + 客戶端需要在鎖時間內執行的事務所需要的時間(在其他客戶端獲取到鎖之前)
= 鎖自動釋放時間 + 客戶端處理業務(鎖期間)的時間
考慮到互斥的保證,這個時間視窗應該限制在鎖被獲取之後

複製程式碼

基本操作介紹了,現在可以優化了

Redlock演算法

在分散式版本中,我們假設我們有N個redis 的matser,各自獨立,沒有任何關係(不在一個cluster下),可以部署在不同的伺服器或是虛擬機器上,假設N設定成5

客戶端獲取鎖的操作:
1. 客戶端A獲取當前時間戳
2. 客戶端A嘗試在所有N個redis中獲取鎖(有序的),使用同樣的key和value,這一步中,由於需要遍歷去請求多個redis服務(set的命令請求,之前理解成業務的處理時間),可能導致阻塞,需要設定超時時間,假設鎖自動釋放的時間是10s,那麼這個超時時間可以設定在 5-50毫秒範圍,這一步,防止客戶端在獲取鎖期間由於redis節點崩潰不可用導致獲取鎖曹氏
3. 客戶端A獲取鎖時,再獲取當前時間戳,與步驟1的時間相減,得到獲取鎖消耗的時間,在鎖有效期內,當且僅當客戶端A拿到大部分(至少3)的鎖時,分散式鎖才可以被正式獲取
4. 每次當鎖被獲取到時(從每個master獲取),有效時間可以被設定成初始有效時間減去獲取鎖消耗的時間 (因為已經獲取鎖了,所以獲取鎖的時間不用算進去)
5. 假設在步驟2中客戶端A獲取不到鎖,比如拿不到大部分的鎖,或者是鎖還沒超時,它需要把自己在少部分redis上拿到的鎖釋放掉

複製程式碼

這個演算法是非同步的嗎

這個演算法依賴的一個假設:是不同程式的各自的時鐘精確率(就是表走得快走得慢,而且誤差跟鎖釋放時間比小得多)一樣,而且不(需要)作時間的同步。類似於,現實生活中,每個人都各自使用自己的電腦(以及電腦上的時間),通常也不會有什麼問題;
在這個情況下,我們需要更具體的說明互斥規則:它(互斥規則)保證僅僅只有獲取鎖的哭護短能夠在鎖的有效時間內結束它的工作,減去某個很小的時間差(幾毫秒,用來作補償)

複製程式碼

失敗重試(這裡指的是獲取鎖的失敗)

當客戶端獲取不到鎖,它應該在之後一個隨機時間點重試,這為了避免多個客戶端嘗試同時獲取同一個資源(類似腦裂的情況,大概意思就是競爭了一堆,卻發現,沒人拿到鎖),客戶端拿到鎖越快(早),腦裂的情況越小(或者重試的需要越小),所以理想情況下,客戶端可以同時(多路複用)傳送set命令給各個master

複製程式碼

釋放鎖

鎖釋放步驟很簡單,就是把所有master例項上的鎖釋放,並不需要關心客戶端在該例項上有沒有成功得到鎖

複製程式碼

安全討論

假設一個客戶端能夠獲取大多數例項上的鎖,所有例項都會存在一個key,並且有個相同的超時時間,但是這個key的設定的時間點是不一樣的(就是set的時候都不一樣,因為是順序執行,總會有時間差),所以key會在不同的時間超時。但是當第一個key被至少設成T1,最後一個key被設成T2(超時時間),我們可以確認第一個key會存在至少 MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT 的時間,而且其他的key會失效的更晚,我們要同時將時間設定成這個
當一個key被set(NX)的時候,其他key就無法被別的客戶端set了

----
總的說的就是時間設定還有併發控制
複製程式碼

可靠性討論

1. 自動釋放的鎖最終還能被獲取到;
2. 客戶端通常會協助刪除那些沒獲取到的鎖(步驟5)、鎖獲取到並且工作結束的,使得並不需要等待鎖超時才能重新獲取鎖;
3. 客戶端需要重試獲取鎖,為了資源競爭,他們重試的等待時間應該大於需要從大多數例項上獲取的時間

複製程式碼

相關文章