首頁 > 軟體

redis分散式鎖與zk分散式鎖的對比分析

2022-11-21 14:02:36

在分散式環境下,傳統的jvm級別的鎖會失效,那麼分散式鎖就是非常有必要的一個技術,一般我們可以通過redis,zk等技術來實現我們的分散式鎖

redis實現分散式鎖

原理

我們都知道redis的處理讀寫請求是單執行緒的,這種情況就不會發生並行的問題,其實實現起來很簡單,就是使用redis的 setnx 命令實現,該命令如果redis中存在當前key,就會返回0,否者插入成功。

那麼就可以獲取鎖的時候新增一個k-v值(任意的一個值)到redis,釋放鎖的時候就刪除,這樣就使用redis實現了一個分散式鎖,相當於分散式中所謂的鎖概念其實就相當一個redis或者zk中的一個值,有這個值就加鎖成功 

能實現的鎖型別

1、普通的鎖

2、讀寫鎖:大致就是給當前的key設定一個特定的值標識當前鎖是讀鎖還是寫鎖,讀與讀之間不互斥,相當與就是沒有鎖,讀與寫,寫於寫之間進行互斥,這個鎖的目的是為了解決快取一致性的問題,這個問題下面來分析

3、紅鎖:底層原理涉及到redis半數寫入機制,針對主從架構中主節點掛了但是資料還未同步到從節點的問題,實現的方式就是當一半以上的節點都寫入成功了才返回給使用者端成功的提示,而不是主節點寫入成功就返回,但是這種情況下的效率比較慢

注意事項 

1、死鎖的情況:出現死鎖的情況有以下幾種情

(1)應用程式沒有正常的釋放鎖:比如程式丟擲異常之類導致釋放鎖程式碼沒有執行; 

解決方案:需要把釋放鎖的程式碼寫在finally模組裡面。

(2)鎖還沒有釋放redis宕機:這個時候本來應該刪除的key因為redis服務停掉了導致刪除不成功,出現死鎖的問題 

解決方案:給每一個key設定一個超時時間,超時了自動清除。

2、鎖永久失效的情況:出現原因是因為當前執行緒A還沒有執行完然後鎖因為過期時間的原因自動刪除了,這個時候其他執行緒B又能拿到這個鎖在redis中建立一個對應的k-v值,然後執行緒A執行到釋放鎖的時候會刪除掉對應key的值,這個時候刪除的值是執行緒B對應的鎖,而不是執行緒A的,這樣在高並行的情況下就有可能導致鎖壓根不生效 

解決方案:在進行設值的時候,value值設定成能標識當前執行緒的一個值,比如在當前執行緒中建立一個uuid,然後在釋放鎖的時候也要比較value值,相同的情況就表示是當前執行緒對應的鎖,允許釋放,否則不允許釋放。

3、會存在鎖提前釋放的問題:當然這個問題也是引起上面第2個問題的根本原因,但是解決方案是不一樣的 

解決方案:在獲得鎖之後,處理業務邏輯的過程中,新建一個timer來定時的去重置鎖的生命週期,當然前提是當前業務邏輯還在執行,這個定時的頻率一般設定為鎖生命週期的1/3,redisson中的 **看門狗 **其實內部就是這樣實現。 

4、主從結構中鎖丟失:上面 紅鎖 已經說明了情況

zk實現分散式鎖

原理

zk實現分散式鎖的原理其實和redis很像,都是往裡面插入對應的值,通過zk的create命令來實現,zk中的值是通過樹形結構,類似與資料夾的層級目錄一樣,如果當前節點存在那麼create命令就會執行失敗,這種情況就代表其他的執行緒已經獲取到了鎖,當前執行緒通過get -w /xxx的命令對當前鎖進行監聽,如果當前鎖被其他執行緒釋放,那麼當前執行緒會重新參與競爭鎖 

能實現的鎖型別

1、非公平鎖:就是通過create建立節點,誰建立成功誰就獲得了鎖,其他鎖對這個節點進行監聽,當釋放鎖的時候,所有執行緒又來競爭這個鎖,但是這種情況會引發羊群效應,就是當一個節點被釋放的時候所有的執行緒都會來競爭,浪費效能

2、公平鎖:通過zk的臨時有序節點來實現,當前執行緒建立一個臨時順序節點,然後判斷當前節點是不是最小的節點,如果時就獲得鎖,如果不是那麼就監聽他的上一個節點,等到釋放鎖的時候會通知後一個節點,然後重複以上判斷,這個就是公平鎖的實現方案,這樣就可以避免羊群效應,減輕伺服器的壓力,但是這種情況可能會發生幽靈節點的產生導致死鎖

幽靈節點:就是使用者端傳送建立命令之後,zk已經成功建立,但是在響應的時候發生了宕機,這個時候使用者端以為沒有成功,但是伺服器端實際上已經有了,但是這個使用者端不知道,就不會去釋放,就造成了幽靈節點,通過 Protection模式能夠避免這個問題,這個的本質就是在節點前面加上一個唯一的標識,如uuid,人使用者端再次請求的時候會比較這個uuid,如果有就認為建立成功了,使用curator的protection模式原理就是這樣的,一下附一張公平鎖實現原理圖:

3、讀寫鎖:實現原理和公平鎖差不多,只是在建立每一個節點的時候標識當前節點時讀鎖(加read標識)還是寫鎖(加write標識)

兩種鎖的對比

分散式系統中通常要考慮CAP的,一致性,可用性和分割區容錯性,很多場景下是很難同時保證CAP的,這個時候就得做出取捨,分散式鎖也是這樣的。

redis分散式鎖:

  • 優點:效能高,能保證AP,保證其高可用,
  • 缺點:但是不能保證其一致性,原因就是在redis叢集+主從的結構中,資料是通過分片儲存的,但是這個時候當一個master節點掛了之後,slave節點還未同步到master節點的資料,導致資料丟失,萬一丟失的資料剛好是你的鎖,那麼就有可能造成並行問題,所以不能保證強一致性,這種情況下可以通過redisson的紅鎖來解決,解決的原理其實就是redis的半數寫入機制,但是這樣完全降低了redis的效能,所以一般情況下是不採用的,zk其實能保證其一致性的原因就是其半數寫入機制加上其 leader選舉的邏輯實現

zk分散式鎖:

  • 優點:能夠保證其一致性,每個節點的建立都會同時寫入leader和follwer節點,半數以上寫入成功才返回,如果leader節點掛了之後選舉的流程會優先選舉zxid(事務Id)最大的節點,就是選資料最全的,又因為半數寫入的機制這樣就不會導致丟資料(ZAB協定)
  • 缺點:效能沒有redis高 

以上已經說明了兩種分散式鎖實現的方式及其原理,怎麼選擇以實際業務為準

快取一致性:資料庫於快取的結果不一樣

雙寫一致性問題(圖網上找的,懶得畫了)

讀寫一致性問題

解決方案:

(1)對於我們的使用者自己的訂單資料,或者使用者資訊資料,或者說高並行場景下能容忍短時間的資料不一致,這些都可以採用新增過期時間(本來進入到快取的資料就不要求強一致性,這種方式能解決大多數的情況,這種方式比較推薦也最常用)

(2)延遲雙刪:就是在更新了資料庫之後等一小段時間再刪除快取(這種的缺點就是對於非高並行的請求,出現快取一致性的問題概率本來就不大,但是卻人為的降低了程式碼的效能)

(3)採用我們redis提供的分散式讀寫鎖,讀讀之間不阻塞,一旦有資料更新那麼讀的操作就阻塞,等待更新+刪除快取的操作結束之後再進行讀,這個情況雖然能真正的保證資料的一致性,但是加鎖了之後會進行阻塞,高並行情況下的效能就降低了

(4)使用阿里巴巴的cannal元件,類似於一個mysql的slave節點,監聽著mysql的binlog紀錄檔檔案,有變化就會主動的通知我們(推薦)

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com