分布式鎖,顧名思義,分布式鎖就是分布式場景下的鎖,比如多臺不同機器上的進程,去競爭同一項資源,就是分布式鎖。
分布式鎖特性
互斥性:鎖的目的是獲取資源的使用權,所以只讓一個競爭者持有鎖,這一點要盡可能保證;
安全性:避免鎖因為異常永遠不被釋放。當一個競爭者在持有鎖期間內,由于意外崩潰而導致未能主動解鎖,其持有的鎖也能夠被兜底釋放,并保證后續其它競爭者也能加鎖;
對稱性:同一個鎖,加鎖和解鎖必須是同一個競爭者。不能把其他競爭者持有的鎖給釋放了。
可靠性:需要有一定程度的異常處理能力、容災能力。
?
分布式鎖的實現
?
1.直接用Redis的setnx命令
首先,當然是搭建一個最簡單的實現方式, 直接用Redis的setnx命令, 這個命令的語法是: setnx key value如果key不存在,則會將key設置為value,并返回1;如果key存在,不會有任務影響,返回0。
基于這個特性,我們就可以用setnx實現加鎖的目的:通過setnx加鎖,加鎖之后其他服務無法加鎖,用完之后,再通過delete解鎖
就是這個獲取鎖的過程就是setnx的過程?如果有鎖了?那就會返回0?如果沒鎖?就會把key設置為value然后釋放鎖就會delete
2.支持過期時間
最簡化版本有一個問題:如果獲取鎖的服務掛掉了,那么鎖就一直得不到釋放,就像石沉大海,查無音信。所以,我們需要一個超時來兜底。
Redis中有expire命令,用來設置一個key的超時時間。 但是setnx和expire不具備 原子性,如果setnx獲取鎖之后,服務掛掉(還沒來的及設置時間),依舊是泥牛入海。
很自然,我們會想到,set和expire, 有沒有原子操作?
當然有,Redis早就考慮到了這種場景,推出了如下執行語句: set key value nx ex seconds nx表示具備setnx特定,ex表示增加了過期時間,最后一個參數就是過期時間的值。
他把set和expire合成了一個原子操作
這個過期和釋放鎖是并列的?就是主動釋放鎖和過期都會delete
3.加上Owner
我們來試想一下如下場景:服務A獲取了鎖,由于業務流程比較長,或者網絡延遲、GC卡頓等原因,導致鎖過期,而業務還會繼續進行。這時候,業務B已經拿到了鎖,準備去執行,這個時候服務A恢復過來并做完了業務,就會釋放鎖,而B卻還在繼續執行。
在真實的分布式場景中,可能存在幾十個競爭者,那么上述情況發生概率就很高,導致同一份資源頻繁被不同競爭者同時訪問,分布式鎖也就失去了意義。
基于這個場景,我們可以發現,問題關鍵在于,競爭者可以釋放其他人的鎖。(也就是說?只能這個鎖持有者自己釋放了鎖才行?就是這個delete過程?只能自己來刪除?而不能其他線程刪除)那么在異常情況下,就會出現問題,所以我們可以進一步給出解決方案: 分布式鎖需要滿足誰申請誰釋放原則,不能釋放別人的鎖,也就是說,分布式鎖,是要有歸屬的。
我們獲取鎖之前不是會有一個delete釋放鎖的過程嗎??我們讓其他線程沒法執行這個delete操作?只有我鎖持有者可以delete
具體步驟?釋放前先檢測是否是持有者要delete?然后返回檢查結果?然后再根據結果進行操作
4.引入LUA
釋放前先檢測是否是持有者要delete?然后返回檢查結果?然后再根據結果進行操作
這三步不是原子的
我們來看?檢測和返回結果之間的間隙
這個同時鎖過期了?
檢測?這鎖過期釋放了鎖?且有其他客戶端獲取到了鎖?然后返回結果? 刪除鎖?
這個時候?已經判斷完了它是自己的鎖?他就會刪除這個鎖
這就造成了鎖的誤刪
然后LUA給這仨操作合成原子操作了
(其實owner )
分布式鎖的可靠性靠什么保證
主從容災
emmm?就是主庫寄了?用從庫頂一頂先
但是主從切換,需要人工參與,會提高人力成本。不過Redis已經有成熟的解決方案,也就是哨兵模式,可以靈活自動切換,不再需要人工介入。
一堆箭頭亂七八糟的?描述一下?每個哨兵會監視其他兩個哨兵的?同時還會監視主庫和兩個從庫?
通過增加從節點的方式,雖然一定程度解決了單點的容災問題,但并不是盡善盡美的,由于同步有時延,Slave可能會損失掉部分數據,分布式鎖可能失效,這就會發生短暫的多機獲取到執行權限。
更妥善的方法
多機部署
如果對一致性的要求高一些, 可以嘗試多機部署,比如Redis的RedLock, 大概的思路就是多個機器,通常是奇數個,達到一半以上同意加鎖才算加鎖成功,這樣,可靠性會向ETCD靠近。
現在假設有5個Redis主節點,基本保證它們不會同時宕掉,獲取鎖和釋放鎖的過程中,客戶端會執行以下操作:
1.向5個Redis申請加鎖;
2.只要超過一半,也就是3個Redis返回成功,那么就是獲取到了鎖。如果超過一半失敗, 需要向每個Redis發送解鎖命令;
3.由于向5個Redis發送請求,會有一定時耗,所以鎖剩余持有時間,需要減去請求時間。這個可以作為判斷依據,如果剩余時間已經為0,那么也是獲取鎖失敗:
4.使用完成之后,向5個Redis發送解鎖請求。
這種模式的好處在于,如果掛了2臺Redis,整個集群還是可用的,給了運維更多時間來修復。
(這種方法太重了?業務很少會用的到)
?
分布式系統三大困境
簡稱NPC
這種模式的好處在于,如果掛了2臺Redis,整個集群還是可用的,給了運維更多時間來修復。
另外,多說一句,單點Redis的所有手段,這種多機模式都可以使用,比如為每個節點配置哨兵模式,由于加鎖是
-半以上同意就成功,那么如果單個節點進行了主從切換,單個節點數據的丟失,就不會讓鎖失效了。這樣增強了
可靠性。
N:Network Delay (網絡延遲)當分布式鎖獲得返回包的時間過長,此時可能雖然加鎖成功,但是已經時過境遷,鎖可能很快過期。RedLock算 了做了些考量,也就是前面所說的鎖剩余持有時間,需要減去請求時間,如此一來,就可以一定程度解決網絡延遲的問題。
P: Process Pause (進程暫停)比如發生GC,獲取鎖之后GC了,處于GC執行中,然后鎖超時。其他鎖獲取,這種情況幾乎無解。這時候GC回來了,那么兩個進程就獲取到了同一個分布式鎖。
也許你會說,在GC回來之后,可以再去查一次啊?
這里有兩個問題,首先你怎么知道GC回來了?這個可以在做業務之前,通過時間,進行一個粗略判斷,但也是很吃場景經驗的;第二,如果你判斷的時候是ok的,但是判斷完GC了呢?這點RedLock是無法解決的。
?
C: Clock Drift (時鐘漂移)
如果競爭者A,獲得了RedLock,在5臺分布式機器上都加上鎖。為了方便分析,我們直接假設5臺機器都發生了時鐘漂移,鎖瞬間過期了。這時候競爭者B拿到了鎖,此時A和B拿到了相同的執行權限。
根據上述的分析.可以看出,RedLock也不能扛住NPC的挑戰,因此,單單從分布式鎖本身出發,完全可靠是不可能的。要實現一個相對可靠的分布式鎖機制,還是需要和業務的配合,業務本身要冪等可重入,這樣的設計可以省卻很多麻煩。
?