我们的系统需要什么样的分布式锁?
原文鏈接:https://developer.aliyun.com/article/766784?utm_content=g_1000150527
一 從單機鎖到分布式鎖
在單機環(huán)境中,當(dāng)共享資源自身無法提供互斥能力的時候,為了防止多線程/多進(jìn)程對共享資源的同時讀寫訪問造成的數(shù)據(jù)破壞,就需要一個第三方提供的互斥的能力,這里往往是內(nèi)核或者提供互斥能力的類庫,如下圖所示,進(jìn)程首先從內(nèi)核/類庫獲取一把互斥鎖,拿到鎖的進(jìn)程就可以排他性的訪問共享資源。演化到分布式環(huán)境,我們就需要一個提供同樣功能的分布式服務(wù),不同的機器通過該服務(wù)獲取一把鎖,獲取到鎖的機器就可以排他性的訪問共享資源,這樣的服務(wù)我們統(tǒng)稱為分布式鎖服務(wù),鎖也就叫分布式鎖。
由此抽象一下分布式鎖的概念,首先分布式鎖需要是一個資源,這個資源能夠提供并發(fā)控制,并輸出一個排他性的狀態(tài),也就是:
鎖 = 資源 + 并發(fā)控制 + 所有權(quán)展示
以常見的單機鎖為例:
- Spinlock = BOOL +CAS(樂觀鎖)
- Mutex = BOOL + CAS + 通知(悲觀鎖)
Spinlock 和 Mutex 都是一個 Bool 資源,通過原子的 CAS 指令:當(dāng)現(xiàn)在為 0 設(shè)置為 1,成功的話持有鎖,失敗的話不持有鎖,如果不提供所有權(quán)的展示,例如 AtomicInteger,也是通過資源(Interger)+ CAS,但是不會明確的提示所有權(quán),因此不會被視為一種鎖,當(dāng)然,可以將“所有權(quán)展示”這個更多地視為某種服務(wù)提供形式的包裝。
單機環(huán)境下,內(nèi)核具備“上帝視角”,能夠知道進(jìn)程的存活,當(dāng)進(jìn)程掛掉的時候可以將該進(jìn)程持有的鎖資源釋放,但發(fā)展到分布式環(huán)境,這就變成了一個挑戰(zhàn),為了應(yīng)對各種機器故障、宕機等,就需要給鎖提供了一個新的特性:可用性。
如下圖所示,任何提供三個特性的服務(wù)都可以提供分布式鎖的能力,資源可以是文件、KV 等,通過創(chuàng)建文件、KV 等原子操作,通過創(chuàng)建成功的結(jié)果來表明所有權(quán)的歸屬,同時通過 TTL 或者會話來保證鎖的可用性。
二 分布式鎖的系統(tǒng)分類
根據(jù)鎖資源本身的安全性,我們將分布式鎖分為兩個陣營:
- 基于異步復(fù)制的分布式系統(tǒng),例如 mysql,tair,redis 等。
- 基于 paxos 協(xié)議的分布式一致性系統(tǒng),例如 zookeeper,etcd,consul 等。
基于異步復(fù)制的分布式系統(tǒng),存在數(shù)據(jù)丟失(丟鎖)的風(fēng)險,不夠安全,往往通過 TTL 的機制承擔(dān)細(xì)粒度的鎖服務(wù),該系統(tǒng)接入簡單,適用于對時間很敏感,期望設(shè)置一個較短的有效期,執(zhí)行短期任務(wù),丟鎖對業(yè)務(wù)影響相對可控的服務(wù)。
基于 paxos 協(xié)議的分布式系統(tǒng),通過一致性協(xié)議保證數(shù)據(jù)的多副本,數(shù)據(jù)安全性高,往往通過租約(會話)的機制承擔(dān)粗粒度的鎖服務(wù),該系統(tǒng)需要一定的門檻,適用于對安全性很敏感,希望長期持有鎖,不期望發(fā)生丟鎖現(xiàn)象的服務(wù)。
三 阿里云存儲分布式鎖
阿里云存儲在長期的實踐過程中,在如何提升分布式鎖使用時的正確性、保證鎖的可用性以及提升鎖的切換效率方面積累比較多的經(jīng)驗。
1 嚴(yán)格互斥性
互斥性作為分布式鎖最基本的要求,對用戶而言就是不能出現(xiàn)“一鎖多占”,那么存儲分布式鎖是如何避免該情況的呢?
答案是,服務(wù)端每把鎖都和唯一的會話綁定,客戶端通過定期發(fā)送心跳來保證會話的有效性,也就保證了鎖的擁有權(quán)。當(dāng)心跳不能維持時,會話連同關(guān)聯(lián)的鎖節(jié)點都會被釋放,鎖節(jié)點就可以被重新?lián)屨肌_@里有一個關(guān)鍵的地方,就是如何保證客戶端和服務(wù)端的同步,在服務(wù)端會話過期的時候,客戶端也能感知。
如下圖所示,在客戶端和服務(wù)端都維護(hù)了會話的有效期的時間,客戶端從心跳發(fā)送時刻(S0)開始計時,服務(wù)端從收到請求(S1)開始計時,這樣就能保證客戶端會先于服務(wù)端過期。 用戶在創(chuàng)建鎖之后,核心工作線程在進(jìn)行核心操作之前可以判斷是否有足夠的有效期,同時我們不再依賴墻上時間,而是基于系統(tǒng)時鐘來對時間進(jìn)行判斷,系統(tǒng)時鐘更加精確,且不會向前或者向后移動(秒級別誤差毫秒級,同時在 NTP 跳變的場景,最多會修改時鐘的速率)。
在分布式鎖互斥性上,我們是不是做到完美了?并非如此,還是存在一種情況,業(yè)務(wù)基于分布式鎖服務(wù)的訪問互斥會被破壞。
我們來看下面的例子:如下圖所示,客戶端在時間點(S0)嘗試去搶鎖,在時間點(S1)在后端搶鎖成功,因此也產(chǎn)生了一個分布式鎖的有效期窗口。在有效期內(nèi),時間點(S2)做了一個訪問存儲的操作,很快完成,然后在時間點(S3)判斷鎖的有效期依舊成立,繼續(xù)執(zhí)行訪問存儲操作,結(jié)果這個操作耗時良久,超過了分布式鎖的過期時間,那么可能這個時候,分布式鎖已經(jīng)被其他客戶端搶占成功,進(jìn)而出現(xiàn)兩個客戶端同時操作同一批數(shù)據(jù)的可能性,這種可能性是存在的,雖然概率很小。
針對這個場景,具體的應(yīng)對方案是在操作數(shù)據(jù)的時候確保有足夠的鎖有效期窗口,當(dāng)然如果業(yè)務(wù)本身提供回滾機制的話,那么方案就更加完備,該方案也在存儲產(chǎn)品使用分布式鎖的過程中被采用。
還有一個更佳的方案,即,存儲系統(tǒng)本身引入 IOFence 能力。這里就不得不提 Martin Kleppmann 和 redis 的作者 antirez 之間的討論了。redis 為了防止異步復(fù)制導(dǎo)致的鎖丟失的問題,引入了 redlock,該方案引入了多數(shù)派的機制,需要獲得多數(shù)派的鎖,最大程度的保證了可用性和正確性,但仍然有兩個問題:
- 墻上時間的不可靠(NTP 時間)
- 異構(gòu)系統(tǒng)的無法做到嚴(yán)格正確性
墻上時間可以通過非墻上時間 MonoticTime 來解決(redis 目前仍然依賴墻上時間),但是異構(gòu)系統(tǒng)只有一個系統(tǒng)并沒有辦法保證完全正確。如下圖所示,Client1 獲取了鎖,在操作數(shù)據(jù)的時候發(fā)生了 GC,在 GC 完成時候丟失了鎖的所有權(quán),造成了數(shù)據(jù)不一致。
因此需要兩個系統(tǒng)同時協(xié)作來完成一個完全正確的互斥訪問,在存儲系統(tǒng)引入 IOFence能力,如下圖所示,全局鎖服務(wù)提供全局自增的 token,Client 1 拿到鎖返回的 token 是 33,并帶入存儲系統(tǒng),發(fā)生 GC,當(dāng) Client 2 搶鎖成功返回 34,帶入存儲系統(tǒng),存儲系統(tǒng)會拒絕 token 較小的請求,那么經(jīng)過了長時間 full gc 重新恢復(fù)后的 Client 1 再次寫入數(shù)據(jù)的時候,因為存儲層記錄的 token 已經(jīng)更新,攜帶 token 值為 33 的請求將被直接拒絕,從而達(dá)到了數(shù)據(jù)保護(hù)的效果(chubby 的論文中有講述,也是 Martin Kleppmann 提出的解決方案)。
這與阿里云分布式存儲平臺盤古的設(shè)計思路不謀而合,盤古支持了類似 IO Fence 的寫保護(hù)能力,引入 Inline File 的文件類型,配合 SealFile 操作,這就有著類似 IO Fence 的寫保護(hù)能力。首先,SealFile 操作用來關(guān)閉已經(jīng)打開的 cs 上面的文件,防止舊的 Owner 繼續(xù)寫數(shù)據(jù);其次,InlineFile 可以防止舊的 Owner 打開新的文件。這兩個功能事實上也是提供了存儲系統(tǒng)中的 Token 支持。
2 可用性
存儲分布式鎖通過持續(xù)心跳來保證鎖的健壯性,讓用戶不用投入很多精力關(guān)注可用性,但也有可能異常的用戶進(jìn)程持續(xù)占據(jù)鎖。針對該場景,為了保證鎖最終可以被調(diào)度,提供了可以安全釋放鎖的會話加黑機制。
當(dāng)用戶需要將發(fā)生假死的進(jìn)程持有的鎖釋放時,可以通過查詢會話信息,并將會話加黑,此后,心跳將不能正常維護(hù),最終導(dǎo)致會話過期,鎖節(jié)點被安全釋放。這里我們不是強制刪除鎖,而是選用禁用心跳的原因如下:
- 刪除鎖操作本身不安全,如果鎖已經(jīng)被其他人正常搶占,此時刪鎖請求會產(chǎn)生誤刪除。
- 刪除鎖后,持有鎖的人會話依然正常,它仍然認(rèn)為自己持有鎖,會打破鎖的互斥性原則。
3 切換效率
當(dāng)進(jìn)程持有的鎖需要被重新調(diào)度時,持有者可以主動刪除鎖節(jié)點,但當(dāng)持有者發(fā)生異常(如進(jìn)程重啟,機器宕機等),新的進(jìn)程要重新?lián)屨?#xff0c;就需要等待原先的會話過期后,才有機會搶占成功。默認(rèn)情況下,分布式鎖使用的會話生命期為數(shù)十秒,當(dāng)持有鎖的進(jìn)程意外退出后(未主動釋放鎖),最長需要經(jīng)過很長時間鎖節(jié)點才可以被再次搶占。
要提升切換精度,本質(zhì)上要壓縮會話生命周期,同時也意味著更快的心跳頻率,對后端更大的訪問壓力。我們通過對進(jìn)行優(yōu)化,使得會話周期可以進(jìn)一步壓縮。
同時結(jié)合具體的業(yè)務(wù)場景,例如守護(hù)進(jìn)程發(fā)現(xiàn)鎖持有進(jìn)程掛掉的場景,提供鎖的 CAS 釋放操作,使得進(jìn)程可以零等待進(jìn)行搶鎖。比如利用在鎖節(jié)點中存放進(jìn)程的唯一標(biāo)識,強制釋放已經(jīng)不再使用的鎖,并重新爭搶,該方式可以徹底避免進(jìn)程升級或意外重啟后搶鎖需要的等待時間。
四 結(jié)語
分布式鎖提供了分布式環(huán)境下共享資源的互斥訪問,業(yè)務(wù)或者依賴分布式鎖追求效率提升,或者依賴分布式鎖追求訪問的絕對互斥。同時,在接入分布式鎖服務(wù)過程中,要考慮接入成本、服務(wù)可靠性、分布式鎖切換精度以及正確性等問題,正確和合理的使用分布式鎖,是需要持續(xù)思考并予以優(yōu)化的。
參考文章
How to do distributed locking - Martin Kleppmann
Is Redlock safe? - antirez
chubby 論文 - google
原文鏈接:https://developer.aliyun.com/article/766784?
版權(quán)聲明:本文中所有內(nèi)容均屬于阿里云開發(fā)者社區(qū)所有,任何媒體、網(wǎng)站或個人未經(jīng)阿里云開發(fā)者社區(qū)協(xié)議授權(quán)不得轉(zhuǎn)載、鏈接、轉(zhuǎn)貼或以其他方式復(fù)制發(fā)布/發(fā)表。申請授權(quán)請郵件developerteam@list.alibaba-inc.com,已獲得阿里云開發(fā)者社區(qū)協(xié)議授權(quán)的媒體、網(wǎng)站,在轉(zhuǎn)載使用時必須注明"稿件來源:阿里云開發(fā)者社區(qū),原文作者姓名",違者本社區(qū)將依法追究責(zé)任。 如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,歡迎發(fā)送郵件至:developer2020@service.aliyun.com 進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,本社區(qū)將立刻刪除涉嫌侵權(quán)內(nèi)容。 與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的我们的系统需要什么样的分布式锁?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 官方剧透:1.11 发版前我们偷看了 F
- 下一篇: 一文读懂人类信息存储进化史