分散式技術原理與演算法解析之分散式鎖 學習筆記 (6)
分散式鎖與分散式互斥的關係:
分散式互斥是在分散式系統中存在多個節點共享某個資源或臨界區,任何時刻只允許一個進程訪問資源或執行臨界區,而分散式鎖正好是解決分散式互斥的一種方法。
- 問題定義:
一句話版:鎖是實現多執行緒同時訪問同一共享資源,保證在同一時刻只有一個執行緒可訪問資源所做的一種標記。(鎖的本質就是一個標記,通俗點是一個變數)
詳細版本:(1)單機鎖是多個執行緒共享的某個變數,操作共享資源前,需要先看這個變數是否為某個值,表示加鎖或解鎖。(2)分散式鎖是多個進程共享的某個變數,其他的和單機鎖類似,鎖被存在公共存儲中,以確保多個進程能看見鎖,確保數據的一致性。
**說明:**當然,作業系統如果掛了,單機也就完全不可用了,如果作業系統沒掛,多執行緒共享某個變數的控制通常是非常健壯的,分散式系統的特點是不會整個集群不可用,不過某台機器不可用的概率非常大,同時網路通訊也是不可靠的,所以,分散式鎖的可靠性就不好保證了,為了保證他的可靠性就需要引入集群,這樣複雜性又會增多了。
設計分散式鎖要考慮的問題:
(1)互斥性,一個資源在同一時間只能被同一機器的執行緒/進程操作。
(2)具備鎖失效機制,防止死鎖。
(3)可重入性,即進程未釋放鎖,可多次訪問臨界資源。
(4)高可用的獲取鎖和釋放鎖的功能,且性能要好。
- 如何實現分散式鎖?
基於關係型資料庫實現分散式鎖
創建一張鎖表,通過操作該表的數據來實現,具體是為申請者在鎖表建立一條記錄,記錄成功則獲得鎖,消除記錄則釋放鎖。
- 優點:簡單。
- 缺點:(1)單點故障:資料庫完蛋,一切都完蛋。(2)死鎖問題:資料庫鎖沒有失效時間,若出現故障,其他進程無法獲得鎖。(3)頻繁讀寫在硬碟上的資料庫導致IO開銷大。
- 適用場景:並發量低,性能要求低。

基於快取實現分散式鎖
由於資料庫的性能(硬碟上)限制了業務的並發量,而基於快取實現分散式鎖的方式,把數據存放在電腦記憶體中,不需要寫入磁碟,減少了 IO 讀寫。
以 Redis 為例:(Redis通過隊列來維持進程訪問共享資源的先後順序)
Redis 通常可以使用 setnx(key, value) 函數來實現分散式鎖,其中 key 表示鎖 id,value = currentTime + timeOut(當前時間 + 超時時間),若某個進程獲得 key 這把鎖後,若在 value 的時間內未釋放鎖,系統就會主動釋放鎖。
setnx 函數的返回值有 0 和 1:
返回 1,說明該伺服器獲得鎖,setnx 將 key 對應的 value 設置為當前時間 + 鎖的有效時間。
返回 0,說明其他伺服器已經獲得了鎖,進程不能進入臨界區。該伺服器可以不斷嘗試 setnx 操作,以獲得鎖。
- 優點:(1)性能更好;(2)快取可跨集群部署;(3)易於實現。
- 缺點:(1)鎖失效的時間的控制不穩定。(2)可靠性不如ZooKeeper。
基於ZooKeeper實現分散式鎖
基於樹形數據存儲結構實現分散式鎖,解決多個進程同時訪問同一臨界資源時,數據的一致性問題。
- 持久節點。這是默認的節點類型,一直存在於 ZooKeeper 中。
- 持久順序節點。在創建節點時,ZooKeeper 根據節點創建時間順序對節點編號。
- 臨時節點。與持久節點不同,當客戶端與 ZooKeeper 斷開連接後,該進程創建的臨時節點就會被刪除。
- 臨時順序節點,就是按時間順序編號的臨時節點。
- 流程:
- 在與該方法對應的持久節點 shared_lock 的目錄下,為每個進程創建一個臨時順序節點。如下圖所示,吹風機就是一個擁有 shared_lock 的目錄,當有人買吹風機時,會為他創建一個臨時順序節點。
- 每個進程獲取 shared_lock 目錄下的所有臨時節點列表,註冊子節點變更的 Watcher,並監聽節點。
- 每個節點確定自己的編號是否是 shared_lock 下所有子節點中最小的,若最小,則獲得鎖。例如,用戶 A 的訂單最先到伺服器,因此創建了編號為 1 的臨時順序節點 LockNode1。該節點的編號是持久節點目錄下最小的,因此獲取到分散式鎖,可以訪問臨界資源,從而可以購買吹風機。
- 若本進程對應的臨時節點編號不是最小的,則分為兩種情況:
- a. 本進程為讀請求,如果比自己序號小的節點中有寫請求,則等待;
- b. 本進程為寫請求,如果比自己序號小的節點中有讀請求,則等待。

- 優點:(1)無單點故障(2)可靠性高(3)易於實現(4)解決了資料庫/快取式鎖的不足
- 缺點:(1)性能沒有快取式分散式鎖好(2)不易理解
- 三種方式的比較





