分布式鎖實現的三種方法


分布式 CAP 理論:

任何一個分布式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多只能同時滿足兩項。

 

鎖只要能保證互斥就行,可以將標記存在內存當中。

單機將標記存在堆內存當中,針對的是多線程

分布式可以采用redis,將標記設在redis緩(公共內存)中,針對的是多進程

 

分布式鎖:保證在分布式部署的應用集群中,同一個方法在同一時間只能被一台機器上的一個線程執行。

 

實現分布式鎖一般有三種實現方式:

1.數據庫樂觀鎖

2.基於Redis的分布式鎖

3.基於Zookeeper的分布式鎖

 

確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件:

1.互斥性。在任意時刻,只有一個客戶端能持有鎖。

2.不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續其他客戶端能加鎖。

3.安全性。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了,即不能誤解鎖。

4.容錯性。只要大多數Redis節點正常運行,客戶端就能夠獲取和釋放鎖。

 

1.基於數據庫的分布式鎖

1)思路:利用主鍵唯一的特性來保證互斥,當有多個請求同時提交到數據庫的話,數據庫會保證只有一個操作可以成功,那么我們可以認為操作成功的那個線程獲得了該方法的鎖,當方法執行完畢之后,想要釋放鎖的話,刪除這條數據庫記錄。

缺點:強依賴數據庫:數據庫掛掉,整個服務就停止運行了

鎖沒有失效時間:一旦解鎖失敗,其它線程不能再獲取到鎖

不可重入:獲取到鎖后,沒有解鎖,無法再次獲取到鎖

2)思路:通過數據庫的行鎖(排他鎖)來保證互斥

缺點:強依賴

不可重入

 

2.基於redis緩存的分布式鎖

 思路:通過多系統連接同一個redis庫,寫入同一個key,通過key的唯一性來保證互斥。如果成功寫入key,則獲取到鎖,然后給key設置過期時間並實現后續的業務操作,業務操作執行完畢后,再刪除key,釋放鎖。

優點:可重入

有失效時間,可自動釋放鎖

缺點:強依賴

使用方法:setnx() 、expire()、getset()、get()

setnx() :方法是原子性的,含義為 SET if Not Exists。如果key不存在,則設置當前的key,並返回1;如果key存在,則設置key失敗,返回0。

expire(): 設置key的過期時間

get(): 獲取key的value

getset():方法是原子性的。對 key 設置 newValue 這個值,並且返回 key 原來的舊值。原來的key不存在,則返回null

操作步驟:

1、setnx(lockkey, 1) 如果返回 0,則說明占位失敗;如果返回 1,則說明占位成功

2、expire() 命令對 lockkey 設置超時時間,為的是避免死鎖問題。

3、執行完業務代碼后,可以通過 delete 命令刪除 key。

容易出現,步驟1執行完,但是步驟2還未執行的時候,服務器宕機,則會出現死鎖情況

調整后的操作步驟:

1、setnx(lockkey, 當前時間+過期超時時間),如果返回 1,則獲取鎖成功,繼續執行步驟2;如果返回 0 則沒有獲取到鎖,轉向 步驟4。

2、expire() 命令對 lockkey 設置超時時間。

3、執行完業務代碼后,可以通過 delete 命令刪除 key。流程結束。

4、get(lockkey) 獲取值 oldExpireTime ,並將這個 value 值與當前的系統時間進行比較,如果小於當前系統時間,則認為這個鎖已經超時,可以允許別的請求重新獲取,繼續執行步驟5。

5、計算 newExpireTime = 當前時間+過期超時時間,然后 getset(lockkey, newExpireTime) 會返回當前 lockkey 的值currentExpireTime。

6、判斷 currentExpireTime 與 oldExpireTime 是否相等,如果相等,說明當前 getset 設置成功,獲取到了鎖。如果不相等,說明這個鎖又被別的請求獲取走了,那么當前請求可以直接返回失敗,或者繼續重試。

7、在獲取到鎖之后,當前線程可以開始自己的業務處理,當處理完畢后,比較自己的處理時間和對於鎖設置的超時時間,如果小於鎖設置的超時時間,則直接執行 delete 釋放鎖;如果大於鎖設置的超時時間,則不需要對鎖進行處理。

 

上述步驟,基本保證了鎖的正常功能,但是一旦redis服務掛掉,則整個系統就停止工作了,所以為了保證redis的高可用性,算法又增加了以下步驟:

建立 N 個完全獨立的 Redis 節點(通常情況下 N 可以設置成 5)。通過獲取所有redis服務的鎖,如果獲取到鎖的redis節點數量大於等於3則表明獲取鎖成功。這樣可以保證在大部分redis節點都存活的情況下,系統仍然可用。

算法步驟:

1、客戶端獲取當前時間,以毫秒為單位。

2、客戶端嘗試獲取 N 個節點的鎖,(每個節點獲取鎖的方式和前面的單redis節點獲取鎖的操作步驟一樣),N 個節點以相同的 key 和 value 獲取鎖。客戶端需要設置接口訪問超時,接口超時時間需要遠遠小於鎖超時時間,比如鎖自動釋放的時間是 10s,那么接口超時大概設置 5-50ms。這樣可以在有 redis 節點宕機后,訪問該節點時能盡快超時,而減小鎖正常使用時間。

3、客戶端計算在獲得鎖的時候花費了多少時間,方法是用當前時間減去在步驟1獲取的時間,只有客戶端獲得了超過 3 個節點的鎖,而且獲取鎖的時間小於鎖的超時時間,客戶端才獲得了分布式鎖。

4、客戶端獲取鎖的時間為設置的鎖超時時間減去步驟3計算出的獲取鎖花費時間。

5、如果客戶端獲取鎖失敗了,客戶端會依次刪除獲取到的redis節點的鎖。

 

3.基於redisson的分布式鎖

redisson是redis官方的分布式鎖組件。GitHub 地址:https://github.com/redisson/redisson

鎖超時時間設置:每獲得一個鎖時,只設置一個很短的超時時間,同時起一個線程在每次快要到超時時間時去刷新鎖的超時時間。在釋放鎖的同時結束這個線程。

 

4.基於zookeeper的分布式鎖

原理:利用臨時節點的唯一性來保證互斥。每個鎖占用一個普通節點 /lock,當需要獲取鎖時在 /lock 目錄下創建一個臨時節點,創建成功則表示獲取鎖成功,失敗則 watch(監聽)節點,有刪除操作后再去爭鎖。臨時節點好處在於當進程掛掉后能自動上鎖的節點自動刪除即取消鎖。

缺點:所有失敗的進程都監聽 鎖的父節點。容易發生羊群效應,即當釋放鎖后所有等待進程一起來創建節點,並發量很大。

針對上述步驟進行優化:

獲取鎖改為創建臨時有序節點,每個獲取鎖的節點均能創建節點成功,只是其序號不同。只有序號最小的可以擁有鎖,如果這個節點序號不是最小的則 watch 序號比本身小的前一個節點 (公平鎖)。

步驟:

1.在 /lock 節點下創建一個有序臨時節點 (EPHEMERAL_SEQUENTIAL)。

2.判斷創建的節點序號是否最小,如果是最小則獲取鎖成功。不是則取鎖失敗,然后 watch 序號比本身小的前一個節點。

3.當取鎖失敗,設置 watch 后則等待 watch 事件到來后,再次判斷是否序號最小。

4.取鎖成功則執行代碼,最后釋放鎖(刪除該節點)。

優點:有效的解決單點問題,不可重入問題,非阻塞問題以及鎖無法釋放的問題。實現起來較為簡單。

缺點:性能沒有緩存服務高

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM