我們的系統需要什麼樣的分佈式鎖?

簡介:針對共享資源的互斥訪問歷來是很多業務系統需要解決的問題。在分佈式系統中,通常會採用分佈式鎖這一通用型解決方案。本文將就分佈式鎖的實現原理、技術選型以及阿里雲存儲的具體實踐進行論述。

image.png


原文鏈接:https://developer.aliyun.com/article/766784?utm_content=g_1000150527

一 從單機鎖到分佈式鎖

在單機環境中,當共享資源自身無法提供互斥能力的時候,爲了防止多線程/多進程對共享資源的同時讀寫訪問造成的數據破壞,就需要一個第三方提供的互斥的能力,這裏往往是內核或者提供互斥能力的類庫,如下圖所示,進程首先從內核/類庫獲取一把互斥鎖,拿到鎖的進程就可以排他性的訪問共享資源。演化到分佈式環境,我們就需要一個提供同樣功能的分佈式服務,不同的機器通過該服務獲取一把鎖,獲取到鎖的機器就可以排他性的訪問共享資源,這樣的服務我們統稱爲分佈式鎖服務,鎖也就叫分佈式鎖。

image.png

由此抽象一下分佈式鎖的概念,首先分佈式鎖需要是一個資源,這個資源能夠提供併發控制,並輸出一個排他性的狀態,也就是:

鎖 = 資源 + 併發控制 + 所有權展示

以常見的單機鎖爲例:

  • Spinlock = BOOL +CAS(樂觀鎖)
  • Mutex = BOOL + CAS + 通知(悲觀鎖)

Spinlock 和 Mutex 都是一個 Bool 資源,通過原子的 CAS 指令:當現在爲 0 設置爲 1,成功的話持有鎖,失敗的話不持有鎖,如果不提供所有權的展示,例如 AtomicInteger,也是通過資源(Interger)+ CAS,但是不會明確的提示所有權,因此不會被視爲一種鎖,當然,可以將“所有權展示”這個更多地視爲某種服務提供形式的包裝。

單機環境下,內核具備“上帝視角”,能夠知道進程的存活,當進程掛掉的時候可以將該進程持有的鎖資源釋放,但發展到分佈式環境,這就變成了一個挑戰,爲了應對各種機器故障、宕機等,就需要給鎖提供了一個新的特性:可用性。

如下圖所示,任何提供三個特性的服務都可以提供分佈式鎖的能力,資源可以是文件、KV 等,通過創建文件、KV 等原子操作,通過創建成功的結果來表明所有權的歸屬,同時通過 TTL 或者會話來保證鎖的可用性。

image.png

二 分佈式鎖的系統分類

根據鎖資源本身的安全性,我們將分佈式鎖分爲兩個陣營:

  • 基於異步複製的分佈式系統,例如 mysql,tair,redis 等。
  • 基於 paxos 協議的分佈式一致性系統,例如 zookeeper,etcd,consul 等。

基於異步複製的分佈式系統,存在數據丟失(丟鎖)的風險,不夠安全,往往通過 TTL 的機制承擔細粒度的鎖服務,該系統接入簡單,適用於對時間很敏感,期望設置一個較短的有效期,執行短期任務,丟鎖對業務影響相對可控的服務。

基於 paxos 協議的分佈式系統,通過一致性協議保證數據的多副本,數據安全性高,往往通過租約(會話)的機制承擔粗粒度的鎖服務,該系統需要一定的門檻,適用於對安全性很敏感,希望長期持有鎖,不期望發生丟鎖現象的服務。

三 阿里雲存儲分佈式鎖

阿里雲存儲在長期的實踐過程中,在如何提升分佈式鎖使用時的正確性、保證鎖的可用性以及提升鎖的切換效率方面積累比較多的經驗。

1 嚴格互斥性

互斥性作爲分佈式鎖最基本的要求,對用戶而言就是不能出現“一鎖多佔”,那麼存儲分佈式鎖是如何避免該情況的呢?

答案是,服務端每把鎖都和唯一的會話綁定,客戶端通過定期發送心跳來保證會話的有效性,也就保證了鎖的擁有權。當心跳不能維持時,會話連同關聯的鎖節點都會被釋放,鎖節點就可以被重新搶佔。這裏有一個關鍵的地方,就是如何保證客戶端和服務端的同步,在服務端會話過期的時候,客戶端也能感知。

如下圖所示,在客戶端和服務端都維護了會話的有效期的時間,客戶端從心跳發送時刻(S0)開始計時,服務端從收到請求(S1)開始計時,這樣就能保證客戶端會先於服務端過期。 用戶在創建鎖之後,核心工作線程在進行核心操作之前可以判斷是否有足夠的有效期,同時我們不再依賴牆上時間,而是基於系統時鐘來對時間進行判斷,系統時鐘更加精確,且不會向前或者向後移動(秒級別誤差毫秒級,同時在 NTP 跳變的場景,最多會修改時鐘的速率)。

image.png

在分佈式鎖互斥性上,我們是不是做到完美了?並非如此,還是存在一種情況,業務基於分佈式鎖服務的訪問互斥會被破壞。

我們來看下面的例子:如下圖所示,客戶端在時間點(S0)嘗試去搶鎖,在時間點(S1)在後端搶鎖成功,因此也產生了一個分佈式鎖的有效期窗口。在有效期內,時間點(S2)做了一個訪問存儲的操作,很快完成,然後在時間點(S3)判斷鎖的有效期依舊成立,繼續執行訪問存儲操作,結果這個操作耗時良久,超過了分佈式鎖的過期時間,那麼可能這個時候,分佈式鎖已經被其他客戶端搶佔成功,進而出現兩個客戶端同時操作同一批數據的可能性,這種可能性是存在的,雖然概率很小。

image.png

針對這個場景,具體的應對方案是在操作數據的時候確保有足夠的鎖有效期窗口,當然如果業務本身提供回滾機制的話,那麼方案就更加完備,該方案也在存儲產品使用分佈式鎖的過程中被採用。

還有一個更佳的方案,即,存儲系統本身引入 IOFence 能力。這裏就不得不提 Martin Kleppmann 和 redis 的作者 antirez 之間的討論了。redis 爲了防止異步複製導致的鎖丟失的問題,引入了 redlock,該方案引入了多數派的機制,需要獲得多數派的鎖,最大程度的保證了可用性和正確性,但仍然有兩個問題:

  • 牆上時間的不可靠(NTP 時間)
  • 異構系統的無法做到嚴格正確性

牆上時間可以通過非牆上時間 MonoticTime 來解決(redis 目前仍然依賴牆上時間),但是異構系統只有一個系統並沒有辦法保證完全正確。如下圖所示,Client1 獲取了鎖,在操作數據的時候發生了 GC,在 GC 完成時候丟失了鎖的所有權,造成了數據不一致。

image.png

因此需要兩個系統同時協作來完成一個完全正確的互斥訪問,在存儲系統引入 IOFence能力,如下圖所示,全局鎖服務提供全局自增的 token,Client 1 拿到鎖返回的 token 是 33,並帶入存儲系統,發生 GC,當 Client 2 搶鎖成功返回 34,帶入存儲系統,存儲系統會拒絕 token 較小的請求,那麼經過了長時間 full gc 重新恢復後的 Client 1 再次寫入數據的時候,因爲存儲層記錄的 token 已經更新,攜帶 token 值爲 33 的請求將被直接拒絕,從而達到了數據保護的效果(chubby 的論文中有講述,也是 Martin Kleppmann 提出的解決方案)。

image.png

這與阿里雲分佈式存儲平臺盤古的設計思路不謀而合,盤古支持了類似 IO Fence 的寫保護能力,引入 Inline File 的文件類型,配合 SealFile 操作,這就有着類似 IO Fence 的寫保護能力。首先,SealFile 操作用來關閉已經打開的 cs 上面的文件,防止舊的 Owner 繼續寫數據;其次,InlineFile 可以防止舊的 Owner 打開新的文件。這兩個功能事實上也是提供了存儲系統中的 Token 支持。

2 可用性

存儲分佈式鎖通過持續心跳來保證鎖的健壯性,讓用戶不用投入很多精力關注可用性,但也有可能異常的用戶進程持續佔據鎖。針對該場景,爲了保證鎖最終可以被調度,提供了可以安全釋放鎖的會話加黑機制。

當用戶需要將發生假死的進程持有的鎖釋放時,可以通過查詢會話信息,並將會話加黑,此後,心跳將不能正常維護,最終導致會話過期,鎖節點被安全釋放。這裏我們不是強制刪除鎖,而是選用禁用心跳的原因如下:

  • 刪除鎖操作本身不安全,如果鎖已經被其他人正常搶佔,此時刪鎖請求會產生誤刪除。
  • 刪除鎖後,持有鎖的人會話依然正常,它仍然認爲自己持有鎖,會打破鎖的互斥性原則。

3 切換效率

當進程持有的鎖需要被重新調度時,持有者可以主動刪除鎖節點,但當持有者發生異常(如進程重啓,機器宕機等),新的進程要重新搶佔,就需要等待原先的會話過期後,纔有機會搶佔成功。默認情況下,分佈式鎖使用的會話生命期爲數十秒,當持有鎖的進程意外退出後(未主動釋放鎖),最長需要經過很長時間鎖節點纔可以被再次搶佔。

image.png

要提升切換精度,本質上要壓縮會話生命週期,同時也意味着更快的心跳頻率,對後端更大的訪問壓力。我們通過對進行優化,使得會話週期可以進一步壓縮。

同時結合具體的業務場景,例如守護進程發現鎖持有進程掛掉的場景,提供鎖的 CAS 釋放操作,使得進程可以零等待進行搶鎖。比如利用在鎖節點中存放進程的唯一標識,強制釋放已經不再使用的鎖,並重新爭搶,該方式可以徹底避免進程升級或意外重啓後搶鎖需要的等待時間。

四 結語

分佈式鎖提供了分佈式環境下共享資源的互斥訪問,業務或者依賴分佈式鎖追求效率提升,或者依賴分佈式鎖追求訪問的絕對互斥。同時,在接入分佈式鎖服務過程中,要考慮接入成本、服務可靠性、分佈式鎖切換精度以及正確性等問題,正確和合理的使用分佈式鎖,是需要持續思考並予以優化的。

參考文章

How to do distributed locking - Martin Kleppmann
Is Redlock safe? - antirez
chubby 論文 - google

原文鏈接:https://developer.aliyun.com/article/766784?

版權聲明:本文中所有內容均屬於阿里雲開發者社區所有,任何媒體、網站或個人未經阿里雲開發者社區協議授權不得轉載、鏈接、轉貼或以其他方式複製發佈/發表。申請授權請郵件[email protected],已獲得阿里雲開發者社區協議授權的媒體、網站,在轉載使用時必須註明"稿件來源:阿里雲開發者社區,原文作者姓名",違者本社區將依法追究責任。 如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至:[email protected] 進行舉報,並提供相關證據,一經查實,本社區將立刻刪除涉嫌侵權內容。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章