當(dāng)前位置:首頁 > 公眾號精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]進程請求分布式鎖時一般包含三個階段:1.進程請求獲取鎖;2.獲取到鎖的進程持有鎖并執(zhí)行業(yè)務(wù)邏輯;3.獲取到鎖的進程釋放鎖;下文會按照這個三個階段進行分析。單機Redis獲取鎖從一開始的請求進程通過SETNX命令獲取鎖;127.0.0.1:6379>SETNXredis_lo...


進程請求分布式鎖時一般包含三個階段:1. 進程請求獲取鎖;2. 獲取到鎖的進程持有鎖并執(zhí)行業(yè)務(wù)邏輯;3. 獲取到鎖的進程釋放鎖;下文會按照這個三個階段進行分析。

單機Redis

獲取鎖

從一開始的請求進程通過SETNX命令獲取鎖;
127.0.0.1:6379> SETNX redis_locks 1
(integer) 1
-> 因為存在進程通過SETNX命令獲取到鎖后,執(zhí)行業(yè)務(wù)邏輯期間掛掉,未能釋放鎖,導(dǎo)致死鎖的場景,引入了超時機制用于打破死鎖形成的條件之一(獲取到鎖的進程一直持有鎖),使得鎖即使在獲取鎖的進程崩潰后仍可以通過超時機制得到釋放;
127.0.0.1:6379> SETNX redis_locks 1
(integer) 1
127.0.0.1:6379> EXPIRE redis_locks 60
(integer) 1
-> 引入超時機制后,獲取鎖存在兩條命令,SETNX EXPIRE,前者用于加鎖,后者用于設(shè)置鎖的過期時間,即加鎖過程不再具有原子性;因此亦存在進程通過SETNX獲取到鎖后還未執(zhí)行EXPIRE便掛掉的場景,同樣會導(dǎo)致死鎖;因此Redis在2.6.12版本后擴展了SET命令的參數(shù),使得通過一條命令SET Key Value EX 10 NX即可實現(xiàn)SETNX EXPIRE的效果,保證了獲取鎖的原子性。
127.0.0.1:6379> SET redis_locks 1 EX 60 NX
OK

釋放鎖

從一開始的獲取到鎖的進程執(zhí)行完業(yè)務(wù)邏輯后調(diào)用DEL命令釋放鎖;
-> 引入超時機制后使得鎖的釋放多了一個渠道;如果獲取到鎖的進程執(zhí)行業(yè)務(wù)邏輯的過程中因為GC等原因造成進程暫停,并且因為進程暫停導(dǎo)致鎖觸發(fā)超時機制使得鎖被釋放,另一個進程獲取鎖成功,而當(dāng)前進程重新運行時并不知道自身的鎖已經(jīng)被釋放,會繼續(xù)執(zhí)行業(yè)務(wù)邏輯并且釋放鎖,而這個鎖是被另一個進程持有的;即一個客戶端釋放了其他客戶端持有的鎖,而要解決這個問題顯然要給鎖加上一個持有者的唯一標(biāo)識,如UUID,當(dāng)進程準(zhǔn)備釋放鎖時,首先檢查鎖的標(biāo)識確認該鎖是否屬于自身,只有鎖屬于自身時才會進行釋放;
// uuid可以通過 UUID.randomUUID().toString()獲取
127.0.0.1:6379> SET redis_locks $uuid EX 60 NX
OK
-> 引入唯一標(biāo)識后,鎖的釋放需要檢查鎖標(biāo)識、釋放鎖兩個步驟,顯然兩個步驟并不是原子的;在極端情況下,仍然會存在檢查鎖標(biāo)識時該鎖尚且屬于自身,而檢查完后鎖就因為超時被釋放了,此時另一個進程獲取到了鎖,從而導(dǎo)致當(dāng)前進程仍然存在釋放其他進程鎖的可能性;因此也需要將這兩個步驟變?yōu)樵拥模话闶峭ㄟ^Lua腳本來實現(xiàn);
--- 原子腳本中包含兩個步驟:1)判斷當(dāng)前鎖是否是自己的 2)鎖是自己的進行釋放
if redis.call("GET", KEYS[1]) == ARGV[1]
then
return redis.call("DEL", KEYS[1])
else
return 0
end

優(yōu)化:自動續(xù)期

上述流程雖然已經(jīng)解決了進程持有鎖并進行業(yè)務(wù)邏輯時,鎖已經(jīng)因為過期而自動釋放這個場景下當(dāng)前進程釋放其他進程鎖的問題,而且當(dāng)前進程也可以將業(yè)務(wù)邏輯繼續(xù)運行完成;但如果當(dāng)前業(yè)務(wù)邏輯存在先后因果關(guān)系時,如Read And Modify, Check Then Act等,可能會導(dǎo)致數(shù)據(jù)一致性的問題;
舉個例子:
  1. 該業(yè)務(wù)邏輯用于對數(shù)據(jù)庫某值進行加一,則先獲取鎖的進程讀取數(shù)據(jù)庫當(dāng)前值為1,然后便因為GC陷入進程暫停導(dǎo)致鎖超時;
  2. 此時另一個進程獲取到了鎖,并從數(shù)據(jù)庫讀取當(dāng)前值為1,并進行 1后寫入,此時數(shù)據(jù)庫值為2;
  3. 接著第一個進程從GC中醒來,繼續(xù)執(zhí)行業(yè)務(wù)邏輯,對之前讀到的值1進行 1后寫入,此時數(shù)據(jù)庫值仍為2;而這造成了更新丟失的問題;
因此最好提供一種機制可以對鎖進行續(xù)期的機制,即客戶端開啟一個守護線程,如果當(dāng)前鎖即將過期,但是業(yè)務(wù)邏輯仍未完成,則該線程會自動對鎖進行續(xù)期;
在Java生態(tài)環(huán)境中,Redisson通過看門狗機制實現(xiàn)了自動續(xù)期的功能,我們只需要進行引用即可;并且Redisson的SDK中實現(xiàn)了很多功能,如可重入鎖、樂觀鎖公平鎖讀寫鎖以及下面集群版會提到的RedLock。

集群redis

一般生產(chǎn)環(huán)境通過主從 哨兵機制構(gòu)建redis集群,并通過寫主讀從的機制對外提供服務(wù),對于寫服務(wù)只要主庫寫入成功便返回客戶端,并通過RDB AOF的機制進行異步的主從狀態(tài)同步;
那么在寫主讀從策略下的redis集群中,一個進程通過SET x x EX x NX命令寫主redis并成功獲取到鎖,并且該命令尚未通過AOF進行網(wǎng)絡(luò)同步,如果此時主redis崩潰,哨兵會進行主備切換,而顯然從庫中一定是沒有這個鎖對應(yīng)的鍵-值對的,因此如果此時其他進程嘗試獲取鎖便可能會獲取成功,而這會造成該鎖機制不再滿足互斥性;
上述問題的關(guān)鍵在于因為主從消息同步存在一定的滯后性,因此redis的作者提出了 RedLock 的機制用于解決上述的問題,RedLock的使用存在一個前提:不部署從庫和哨兵機制,但主庫要部署多個,官方推薦為5個(奇數(shù)); 如下圖所示:淺析redis與zookeeper構(gòu)建分布式鎖的異同

獲取鎖

如果一個進程想要在redis集群中獲取到鎖,那么必須使得該進程獲取到鎖這件事在redis集群實例間達成共識,而達成共識一般通過Quorum機制,即少數(shù)服從多數(shù);
因此在redLock算法中,一個進程需要依次向5個實例發(fā)送SET lock uuid EX 60 NX請求,并且記錄響應(yīng)結(jié)果,如果有3(5/2 1,半數(shù) 1)以上實例返回加鎖成功,那么該進程則成功獲取到鎖;獲取鎖失敗則需要向集群中所有redis實例發(fā)起釋放鎖的請求(通過Lua腳本釋放鎖);
上述為不考慮網(wǎng)絡(luò)延遲、進程暫停時鐘漂移這三個會導(dǎo)致數(shù)據(jù)一致性問題的方案,接著基于網(wǎng)絡(luò)的部分同步模型來對算法做進一步的安全性探討;

1. 考慮超出上界的網(wǎng)絡(luò)延遲

進程請求鎖后同步等待redis服務(wù)端的響應(yīng)結(jié)果,如果此時因為網(wǎng)絡(luò)延遲超出上界的緣故,導(dǎo)致請求進程收到redis實例返回的加鎖成功的響應(yīng)時,當(dāng)前鎖已經(jīng)超過EX規(guī)定的時間并自動過期;而該進程對此并不知情仍然進行下一步的業(yè)務(wù)邏輯并在之后釋放鎖,而這會造成與redis單機版類似的問題-當(dāng)前進程釋放了其他進程的鎖;因此redLock在當(dāng)前進程嘗試獲取鎖時會先獲取當(dāng)前時間戳T1,等客戶端收到來自redis服務(wù)端的響應(yīng)時,再次獲取當(dāng)前時間戳T2,并判斷T2-T1 > EX Time,不等式成立時當(dāng)前客戶端才會認為自己加鎖成功,否則加鎖失?。?br />

2. 考慮超出上界的進程暫停

如果進程已經(jīng)獲取到鎖后發(fā)生較長時間的GC亦會如單機版redis一樣,導(dǎo)致當(dāng)前客戶端釋放其他客戶端的鎖,解決方案類似,通過使用Lua腳本進行鎖的釋放;

3. 考慮超出上界的時鐘漂移

如果請求鎖的客戶端獲取到60s的鎖后進行業(yè)務(wù)邏輯的處理,而此時redis集群中一些實例在同步NTP時間時,發(fā)生了大的跳躍,造成一些實例上的鎖提前過期了,這可能會導(dǎo)致同時有兩個客戶端持有集群的redis鎖;舉個例子:
  1. 客戶端A在第一次加鎖時獲取了redis集群實例[1,2,3]的成功響應(yīng),而[4,5]被其他客戶端加鎖,但按照半數(shù)以上的原則,只有客戶端A獲取到了鎖;
  2. 此時實例3發(fā)生了時鐘跳躍導(dǎo)致實例3上的鎖提前過期,而此時另一個客戶端B請求加鎖時獲取到了[3,4,5]三個實例的成功響應(yīng),導(dǎo)致客戶端B也獲取到了鎖;
針對這個問題,redis的作者表示需要通過定期的運維保證集群中的機器不會出現(xiàn)大幅度的跳躍;

4. RedLock獲取鎖的過程

綜上所述,RedLock 獲取鎖的過程如下:
  1. 請求進程記錄下當(dāng)前時間戳T1;
  2. 請求進程依次請求redis實例獲取鎖,并且每個請求都會設(shè)置超時時間(該超時時間遠小于鎖的有效時間),如果請求進程收到響應(yīng)或超過超時時間則繼續(xù)向下一個redis實例申請加鎖;
  3. 如果請求進程獲得了半數(shù)以上的redis集群實例響應(yīng),則獲取當(dāng)前時間戳T2,判斷T2-T1 > EX Time,如果不成立則獲取鎖失??;

釋放鎖

釋放鎖不僅需要通過Lua腳本進行釋放,而且考慮到加鎖期間存在一些redis實例中已經(jīng)添加鎖成功,但是響應(yīng)超時了,而這對于當(dāng)前鎖的持有者是不知情的,因此持有鎖的進程需要向集群中所有的redis實例發(fā)送請求釋放鎖;

zookeeper實現(xiàn)分布式鎖的優(yōu)勢

因為zk基于全序廣播算法ZAB的緣故,zk對于每個進程發(fā)起的獲取鎖的請求,都會分配一個全局唯一遞增的ZXID,即ZXID越小,請求越早到達zk;并且因為zk對于每個請求的處理都會通過執(zhí)行ZAB算法在集群各個節(jié)點間達成共識,所以ZXID最小的請求會獲取到鎖。因此zk不需要依賴額外的RedLock機制來實現(xiàn)分布式共識;這也是zk實現(xiàn)分布式鎖的一個優(yōu)勢。
獲取鎖:獲取鎖即為在zk中創(chuàng)建一個臨時節(jié)點,例如/exclusive_lock/lock;創(chuàng)建臨時節(jié)點成功的進程則獲取到鎖,創(chuàng)建失敗的進程則加鎖失??;我們可以通過開源的zk客戶端,如ZkClient、Curator的create()方法進行節(jié)點的創(chuàng)建;
釋放鎖:因為臨時節(jié)點的特性,釋放鎖存在兩種情況:1. 獲取鎖的進程刪除臨時節(jié)點便釋放了所持有的鎖;2. 獲取鎖的進程掛了,與zk斷連后該臨時節(jié)點會自動刪除,即自動釋放鎖;而這是通過zk獲取鎖的第二個優(yōu)勢-沒有鎖過期帶來的煩惱;回憶一下:redis引入超時過期機制是為了解決獲取鎖節(jié)點宕機的問題,并且因為這個超時過期帶來了很多的問題場景。
并且可以直接通過zk的順序節(jié)點和Watcher機制實現(xiàn)讀寫鎖、樂觀鎖;
Watcher機制:通過Watcher機制,客戶端可以向如下的/read_write_lock目錄節(jié)點注冊子節(jié)點變更的Watcher監(jiān)聽,這樣當(dāng)該目錄下子節(jié)點發(fā)生增減時,zk會將該事件通知所有注冊的客戶端;
順序節(jié)點:在順序節(jié)點目錄下的子節(jié)點,zk會為節(jié)點維護創(chuàng)建的先后順序,并在節(jié)點名稱后綴中增加節(jié)點創(chuàng)建的次序值:
淺析redis與zookeeper構(gòu)建分布式鎖的異同通過上述兩個機制,我們可以實現(xiàn)讀寫鎖
  • 首先需要定義一個機制用于區(qū)分讀寫請求,可以在寫入節(jié)點值時增加Read or Write進行區(qū)分;因為順序節(jié)點后綴大小標(biāo)識了請求的先后性,如上圖所示:表明zk先后收到了兩個獲取Read鎖、一個獲取Wrtie鎖的請求;
  • 接著對于一個進程獲取讀鎖的請求,如果此時/read_write_lock目錄下沒有包含Write的節(jié)點,則直接創(chuàng)建節(jié)點并返回獲取成功;如果此時存在獲取讀鎖的節(jié)點,則獲取失敗,不過仍然會在目錄下創(chuàng)建節(jié)點,但需要在該寫鎖節(jié)點上注冊Watcher監(jiān)聽,當(dāng)該寫鎖節(jié)點刪除后,原請求進程可以嘗試重新獲取讀鎖;
  • 對于一個獲取寫鎖的請求,如果此時/read_write_lock目錄下Write節(jié)點已經(jīng)是存活的后綴最小的節(jié)點,則獲取寫鎖成功;如果在該請求前仍然存在其他節(jié)點,則獲取寫鎖失敗,需要在該目錄下后綴不大于該寫請求的節(jié)點上注冊Watcher通知,這樣當(dāng)該節(jié)點釋放后,則請求進程可以再次嘗試獲取寫鎖。

zookeeper實現(xiàn)分布式鎖的一些問題

當(dāng)然通過zk實現(xiàn)分布式鎖仍然存在很多問題,我們同樣按照網(wǎng)絡(luò)延遲、進程暫停的角度進行分析;
  1. 進程1創(chuàng)建臨時節(jié)點/exclusive_lock/lock成功,拿到了鎖
  2. 進程1因為機器長時間GC而暫停
  3. 進程1無法給 Zookeeper 發(fā)送心跳,Zookeeper將臨時節(jié)點刪除
  4. 進程2創(chuàng)建臨時節(jié)點/exclusive_lock/lock 成功,拿到了鎖
  5. 進程1機器GC結(jié)束后恢復(fù),它仍然認為自己持有鎖(產(chǎn)生沖突)
因此無論是通過zk還是redis還是其他鎖服務(wù),都會存在類似的問題,即獲取到鎖的服務(wù)在持有鎖期間發(fā)生進程暫停導(dǎo)致鎖釋放后,另一個進程獲取倒鎖,導(dǎo)致兩個客戶端都會認為自己持有鎖。
可以使用類似Redisson的續(xù)約機制,通過一個守護線程進行zk臨時節(jié)點的維護;但是zk不會存在釋放了別人鎖的情況,所以不需要通過類似Lua的機制來釋放鎖;

fencing token算法

上述問題的關(guān)鍵在于進程在喚醒后,仍然以為自己持有鎖并進行共享資源的操作;因為操作系統(tǒng)中進程的切換或崩潰后恢復(fù),只會在原有的執(zhí)行序列位置繼續(xù)執(zhí)行,自然不可能自發(fā)的在喚醒后重新檢查自己是否仍然持有鎖;
因此fencing算法提出了讓共享資源具有拒絕持有過期鎖的進程發(fā)起的請求的能力:
  1. fencing算法通過給鎖加上一個序列,即每次請求進程成功獲取鎖時,鎖服務(wù)都會返回一個遞增的token;
  2. 接著請求進程拿著這個token去操作共享資源;
  3. 共享資源緩存token,并拒絕token值較小的客戶端請求。
在該算法下,如先獲取鎖的進程1得到的token = 1;接著該進程GC后進程2獲取到鎖得到的token = 2,接著進程2使用值為2的token請求共享資源,共享資源對該token值進行緩存;最后進程1蘇醒,使用值為1的token請求共享資源,共享資源察覺到1<2(current cached),則拒絕當(dāng)前請求;
通過該算法,可以解決上述的持有鎖后進程暫停帶來的影響;不過如果持有過期鎖的進程操作共享資源并沒有先后因果關(guān)系時,可以無需考慮使用該算法,該算法存在一定的代價。

總結(jié)

一般都是使用分布式鎖用作互斥,上述文章中列舉了NPC場景下的一些問題,并都給出了相應(yīng)的解決方案;具體使用時可以考慮場景本身對于數(shù)據(jù)絕對正確的敏感度,決定是否要使用代價更大的機制來進行保證。當(dāng)然RedLock還是不推薦使用,代價太大,還是建議使用主從 哨兵的機制進行redis集群的搭建。


本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉