聊一聊分佈式鎖的設計模型

一、什麼是分佈式鎖?


什麼是分佈式鎖?對於這個問題,相信很多同學是既熟悉又陌生。隨着分佈式系統的快速發展與廣泛應用,針對共享資源的互斥訪問也成爲了很多業務必須要面對的需求,這個場景下人們通常會引入分佈式鎖來解決問題。我們通常會使用怎麼樣的分佈鎖服務呢?有開源的 MySQL,Redis,ZooKeeper,Etcd 等三方組件可供選擇,當然也有集團內自研的 Tair,Nuwa 等分佈式鎖服務提供方。總的來看,我們對分佈式鎖的需求可以大體劃分爲以下兩類應用場景:

分佈式鎖的業務需求、場景看起來比較簡單,但是事實上我們在使用分佈式鎖過程中,總還是會提出這樣、那樣的新需求,看起來找不到一個分佈式鎖場景的大一統的解決方案。那麼,分佈式鎖內部究竟是怎麼實現的?或者說應該怎麼實現呢?這個是我們這篇文章希望探討的,也希望我們的探討能夠讓讀者朋友對分佈式鎖的原理有一定了解,在做技術選型的時候,也能夠有更多的指導。

二、設計模型


我們應該給分佈式鎖建立怎麼樣的設計模型呢?這個問題可以換個視角來看,我們應該建立怎麼樣的合理性質來打造出一個分佈式鎖模型?我們不妨參考一下來自業界的兩個定義。首先是 Apache Helix(開源社區一個風頭正勁的通用集羣資源管理框架,它能被用作自動管理存在於集羣節點上的分區的,有副本的分佈式資源)對於分佈式鎖管理器的性質定義:a)均勻分佈,不是先開始的節點獲取所有的分佈式鎖;b)再調度的均衡性,需要妥善處理持有分佈式鎖的節點意外退出後的鎖資源分配問題;c)再平衡,當有新的節點加入的時候,節點間的鎖資源應該再分配以達到均衡。看得出來,Helix 對分佈式鎖模型的定義非常強調均衡性,考慮到它是負責集羣內的分區資源調度的,這個側重點並不讓人意外。

圖 1 Helix 提出的分佈式鎖管理器的性質

我們再看另一個大名鼎鼎的 Redis 對分佈式鎖性質的定義,它提出了分佈式鎖模型必須要遵守的三個原則:a)絕對互斥,同一時刻,只有一個客戶端能夠持有分佈式鎖;b)最終可用,如果持有分佈式鎖的客戶端意外退出了,那麼相關的分佈式鎖資源要能夠被重新再分配;c)服務容錯,提供分佈式鎖的服務本身要具備容錯能力,即使部分節點崩潰,也不影響整體的分佈式鎖服務。

圖 2 Redis 提出的分佈式鎖管理器的性質

結合自身的經驗,我們高度認同 Redis 對有關分佈式鎖模型的基本約束條件,這些其實也是實現一個分佈式鎖服務所必須要考慮的幾個屬性。並且,Redis 相關的文章中也繼續探討了分佈式鎖的其它的特性約束。事實上,如下圖 3 所示,我們從三個維度歸納總結一個分佈式鎖模型落地需要考慮的性質。第一個維度是最基本的約束條件,與 Redis 提出的完全一致,我們稱之爲:互斥性,可容錯,最終可用;第二層提出的分佈式鎖管理器需要關注的一些鎖的特性,譬如搶鎖效率,分佈式鎖的均衡性,鎖的切換精度,鎖的可重入性質等等。在這個之上,還有一個分佈式鎖落地時候必須要考慮的事關數據一致性與正確性保證的約束,即可防護性以及應對好時鐘漂移的影響。

圖 3 分佈式鎖設計模型需要考慮的三個維度的性質

關於分佈式鎖管理器實際落地需要考慮的數據一致性與正確性的話題,其中一個話題是牆上時間的不靠譜,這個可以引入非牆上時間 MonoticTime 來解決,本文就不在這個問題上做更多討論。另一個話題,實際使用分佈式鎖服務來訪問共享資源的時候一定要輔助以 Fencing 能力方可做到資源訪問的絕對互斥性。大神 Martin Kleppmann 提供了一個非常好的案例說明,如下圖 4 所示,Client1 首先獲取了分佈式鎖的所有權,在操作數據的時候發生了 GC,在長時間的 "Stop-The-World" 的 GC 過程中丟失了鎖的所有權,Client2 爭搶到了鎖所有權,開始操作數據,結果等 Client1 的 GC 完成之後,就會出現 Client1,Client2 同時操作數據的情形,造成數據不一致。

圖 4 缺乏 Fencing 保護的分佈式鎖可能導致數據不一致

針對上述問題,解決方案是引入共享資源訪問的 IO Fence 能力,如下圖 5 所示,全局鎖服務提供全局自增的 Token,Client1 拿到鎖返回的 Token 是 33,並帶入存儲系統,發生 GC,當 Client2 搶鎖成功返回 Token 34,帶入存儲系統,存儲系統會拒絕後續 Token 小於 34 的請求,那麼經過了長時間 GC 重新恢復後的 Client 1 再次寫入數據的時候,因爲底層存儲系統記錄的 Token 已經更新,攜帶 Token 值爲 33 的請求會被直接拒絕,從而達到了數據保護的效果。

圖 5 基於 Fencing 的數據一致性保障

回到文章的主旨,如何實現一個高效的分佈式鎖管理器呢?首先,拋出一個觀點,分佈式鎖管理器也可以按照控制平面與數據平面進行切分。圖 3 中提到的分佈式鎖性質可以劃分到不同的平面分別負責。我們的這個觀點其實並非首創,事實上在 OSDI'20 的 Best Paper -《Virtual Consensus in Delos》一文,Facebook 的研究團隊針對一致性協議的設計做了深入探討,非常的精彩。文章裏面提到了類似 Raft 這類分佈式一致性協議,裏面也同樣可以分拆出管控平面與數據平面,前者負責容錯、成員變更、角色調整,後者負責定序與持久化。通過解耦兩個平面,一下子讓共識協議變得很靈活。

圖 6 Delos 中 Virtual Consensus 對管控數據平面的觀點

我們分佈式鎖模型的實現是否也可以參考類似的思路呢?將容錯、成員變更等負責的邏輯轉移至管控平面,而數據平面負責分佈式鎖的其它譬如互斥,最終可用,搶鎖效率等等功能。答案是肯定的,好吧,即使這樣的思路也並非我們首創,在數據庫領域,一直有這麼個流派來演進這類的分佈式鎖系統,它們被統稱爲 DLM(Distributed Lock Manager),典型的有 Oracle RAC,GFS2,OCFS2,GPFS,我們接下來好好說道說道 DLM。

三、何謂 DLM?


DLM 的思想來自《The VAX/VMS Distributed Lock Manager》,在 1984 年首次應用於 VAX/VMS V4.0。接下來,我們以 Oracle RAC 爲例,來說明下 DLM 的設計思路。

Oracle RAC 運行於集羣之上,基於內存融合技術,使得 Oracle 數據庫具備高可用性和極致性能。如果集羣內的一個節點發生故障,Oracle 可以繼續在其餘的節點上運行。爲了保證多個節點寫入內存 Page 過程的一致性,使用分佈式鎖管理器 (DLM) 處理分佈式鎖資源的分配和釋放。

如圖 7 所示,DLM 是一個去中心化的設計,集羣中的所有節點都是對等的,每個節點都維護了部分鎖信息。那麼申請鎖時,應該由誰來決定鎖的分配呢 ? 在 DLM 中,每把鎖都有 Master 的概念,由 Master 統一協調、授權,決定是否允許加鎖或解鎖,每個節點都有可能成爲鎖的 Master。每個節點管理這些鎖資源時,將這些鎖資源通過樹狀結構進行組織,通過對樹節點的父子繼承關係可以優化鎖的粒度,提升加解鎖的效率。

圖 7 DLM 的分佈式鎖角色關係

在加鎖或解鎖過程中,涉及到以下幾類節點: a)Requester: 發起加鎖或解鎖的節點;b)DirectoryNode: 鎖的目錄節點,存放着鎖的 Master 被哪個節點鎖持有這類信息;d)Master: 鎖的持有者,實際管理者,負責鎖的分配,釋放。下面我們用具體示例來描述分佈鎖的分配、釋放的具體過程,例子裏面存在 A, B, C 3 個節點,其中 A 爲 Requester,B 爲 DirectoryNode, C 爲 Master 節點。

3.1 加鎖過程

圖 8 是需要到其他節點上加鎖的過程,是所有加鎖情況中最耗時的情況,最多需要 2 輪交互。當資源在本地建立後,後續對於具有繼承關係的資源在本地加鎖就可以了,無需和其他節點進行交互:

  1. 節點 A 對資源 R1 加鎖,首先在本地構造該鎖對象,也稱爲鎖的 shadow,但此時 A 節點並未加鎖成功;

  2. 節點 A,對資源 R1 通過哈希計算出 R1 對應的目錄管理者爲節點 B;

  3. 節點 A 請求節點 B,節點 B 的記錄上顯示 R1 的鎖的 Master 在 C 上;

  4. 節點 A 向節點 C 發起對 R1 加鎖請求;

  5. 節點 C 維護 R1 的鎖請求隊列,如果允許 A 加鎖,則返回成功; 

  6. A 更新本地 R1 鎖 shadow 相關信息,加鎖完成。

圖 8 DLM 的加鎖過程

3.2 解鎖過程

圖 9 展示瞭解鎖的過程,也比較直觀,如下三個步驟:

  1. 節點 A 對資源 R1 解鎖,刪除本地構造該鎖對象; 

  2. 節點 A 請求節點 C,請求將 A 的鎖釋放; 

  3. 若 A 是隊列中最後一個請求者,則節點 C 將發送請求給 B,將  R1 從目錄中摘除,以便後續其他節點能夠成爲鎖的 Master ,否則,C 節點僅僅將 A 從 R1 的加鎖隊列移除。

圖 9 DLM 的解鎖過程

3.3 成員變更

上述的加鎖和解鎖過程,僅僅是普通的一次加解鎖過程。那麼集羣出現節點故障、集羣增刪節點,如何控制分佈式鎖能夠被正常路由和分配呢?在 DLM 中,存在 Connection Manager 角色,除了負責各個節點的網絡通信,還有一個重要功能是在集羣節點發生增刪時,節點間首先選舉出 leader 節點進行協調,每個節點均有可能成爲 leader 節點。在發生節點增加或刪除時會下述過程:

從上述過程,我們可以看到集羣發生節點成員變更時,恢復過程是非常複雜的。爲了減少這種情況的發生,當一個節點通信失敗後,會等待一定時間,超過該間隔後仍無法正常通信,纔會執行刪除節點的流程。一個節點如果僅是發生重啓,沒有達到需要觸發成員變更的閾值,那麼只需要恢復這個節點就可以了。在這個過程中,僅僅該節點的鎖相關信息丟失了,對於集羣的其他節點沒有影響。重啓過程中,發往該節點的請求將會被 Pending 住,直到該節點恢復。

發生重啓的節點,上面大部分鎖仍能恢復。節點上的鎖由兩部分組成,一部分爲 Local Lock,表示發起加鎖的爲節點自身。另一部分爲 Remote Lock,表示由其他節點發起的加鎖。對於 Local Lock,其他節點沒有信息無法恢復,但不存在競爭,也無需恢復;對於 RemoteLock,可以從其他節點的 shadow 信息中進行恢復。

3.4 些許思考

從成員變更過程,我們可以看到,Connection Manager 在 DLM 中承擔了極其關鍵的角色,這個也是整個設計中最爲複雜的地方,當出現節點故障時,由 Connection Manager 統一協調鎖的重新分配,事實上承擔了我們所謂的分佈式鎖管控平面的工作。DLM 的優點是什麼?負責分佈式鎖資源分配的數據平面不用考慮整個系統的容錯,可以很均衡地讓更多機器參與到資源分配,並且鎖資源信息不需要落盤,不需要走共識協議做容錯,只需要關注搶鎖的互斥性和搶鎖效率問題,這個搶鎖效率,服務水平擴展能力都將非常有優勢。

通過上述對 DLM 的加鎖,解鎖及成員變更過程進行剖析,這個裏面還是有比較清晰的管控平面與數據平面的解耦設計,當然,實現過程很複雜,特別是 failover 這塊恢復邏輯。但這種思想還是非常好的,值得我們做架構設計時候借鑑。尤其要提到一點,不同於 DLM 起源的 1980 年代,後期業界有了 Paxos/Raft/EPaxos 等共識協議,我們也有了類似 ZooKeeper/Etcd 等基於共識協議的一致性協議,我們的分佈式鎖管理器的管控平面完全可以用起來這些成熟的三方組件。

四、最佳實踐


阿里雲存儲部門擁有着從塊存儲到文件存儲,對象存儲,日誌存儲,以及表格存儲等全球最完整的存儲產品體系。圖 10 展示了當前存儲產品採用的非常通用的基於分區調度模型的系統架構。整個業務系統按照管控平面與數據平面來劃分,其中數據平面將用戶的存儲空間按照一定規則分割成若干分區,在運行時一個分區會被分配至某個服務器提供服務,一個服務器可以同時加載多個分區。分區不使用本機文件系統存儲持久化數據,其擁有的全部數據均會存儲在盤古分佈式文件系統中的特定目錄。基於如此的分區調度模型,當某個服務器發生宕機的時候,它承載的分區需要被重新調度,快速遷移至其它健康的服務器繼續提供服務。

圖 10 雲存儲基於盤古 + 女媧的通用的分區調度設計框架

在雲存儲的分區調度模型中,有關分區資源的互斥訪問(即任何時刻任一個分區必須至多爲某一臺服務器所加載並提供讀寫訪問服務)是存儲系統提供數據一致性的基石,必須得到保障。事實上,雲存儲的最佳實踐中有着類似 DLM 的設計哲學,將分佈式鎖管理器的容錯問題抽離出來,藉助女媧 - 飛天分佈式協同基礎服務提供的選主功能來實現,進而可以專注在分佈式鎖資源的調度策略:

1)管控調度器負責具體分區資源的互斥分配,這裏結合不同存儲業務的特殊需要,可以演進出不同的調度策略,從分佈式鎖的均衡性,分佈式鎖的搶鎖效率,分佈式鎖的切換精度等不同維度做專項優化;

2)分佈式鎖管理器中最複雜的容錯能力,通過依賴女媧選主功能實現,並且通過女媧的服務發現能力實現管控節點平滑上下線;

3)存儲系統的數據最終均存放在盤古 - 飛天分佈式存儲文件系統,從具體的分區數據,到管控調度的元數據,這些信息都會放入盤古。盤古提供了高可靠高性能的存儲服務以及 Fencing 保護能力,保障數據一致性;

圖 11 基於盤古分佈式文件系統提供的 Fencing 保護

分佈式鎖提供 Fencing 保護的核心點是在訪問共享資源的時候帶上 Token 檢查。盤古作爲存儲的統一的基座,通過引入特殊的 InlineFile 文件類型,配合 SealFile 操作,實現了類似的 IO Fence 保護能力:a)SealFile 操作用來關閉已經打開的文件,防止分佈式鎖舊的佔有者繼續寫數據;b)爲每個分區引入 InlineFile,針對盤古文件的元數據操作關聯 InlineFile 相關的 CAS 判斷,進而可以防止分佈式鎖舊的佔有者打開新的文件。如圖 11 所示,這兩塊功能結合起來事實上也是提供了存儲系統中的寫數據 Token 檢查支持。

我們看到,就雲存儲的 DLM 實現中,有一個通用的基於分區的調度器,有女媧提供容錯保障,由盤古提供資源的 Fencing 保護,這個就是雲存儲的最佳實踐。

五、總結


分佈式鎖提供了分佈式環境下共享資源的互斥訪問,在分佈式系統中應用十分廣泛。這篇文章從分佈式鎖的性質出發,探討了分佈式鎖的模型設計。就分佈式鎖系統,我們探討了控制平面與數據平面解耦的架構設計,並介紹了阿里雲存儲場景下分佈式鎖的最佳實踐。期望我們的分享對於讀者朋友有所幫助。

參考文獻

1.How to do distributed locking -- Martin Kleppmann:https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

2.Distributed Lock Manager -- Apache Helix:https://helix.apache.org/1.0.2-docs/recipes/lock_manager.html

3.Distributed Locks with Redis -- Redis:https://redis.io/docs/reference/patterns/distributed-locks/

4.The VAX/VMS Distributed Lock Manager -- VMS Software:https://wiki.vmssoftware.com/Distributed_Lock_Manager

5.Cache Fusion: Extending Shared-Disk Clusters with Shared Caches -- Oracle RAC:http://www.dia.uniroma3.it/~vldbproc/086_683.pdf

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/uA26VVmYMTfs-dWcLOY04w