分佈式鎖學習

一. 數據庫鎖

建立一張鎖表,當要鎖住某個資源時,就在該表中增長一條記錄,想要釋放鎖的時候就刪除這條記錄。html

二. zookeeper分佈式鎖

Zookeeper 的分佈式鎖是經過臨時節點(EPHEMERAL)實現的。當客戶端會話終止或超時後 Zookeeper 會自動刪除臨時節點。 加鎖流程: 假設鎖空間的根節點爲 /_locknode_node

  1. 客戶端調用create()在 /_locknode_ 下建立臨時有序的子節點,(第一個客戶端子節點爲/_locknode_/lock-0000000000,第二個爲/_locknode_ /lock-0000000001,以此類推);
  2. 客戶端調用getChildren(watch=false)獲取獲取子節點列表,注意wtach設置爲false,以免羊羣效應(Herd Effect);
  3. 若是在步驟1中建立的路徑名具備最低序列號後綴,則客戶端具備鎖,不然在當前節點前一位的節點調用exist(watch=true),並使用下一個最低序列號。
  4. 若是exists()返回false,請轉到步驟2。不然,在轉到步驟2以前,請等待上一步中路徑名的通知。

zookeeper若是長時間沒有檢測到客戶端心跳的時候就會認爲Session過時,那麼這個Session所建立的全部的臨時節點都會被刪除
因此網絡、jvm gc等緣由致使長時間沒有收到心跳,會致使多客戶端操做共享資源。redis

zookeeper在集羣模式下,Client發的請求只會由Leader執行,發給Follwer的請求會轉發給Leader。Leader接收到請求後會通知Follwer進行投票,Follwer把投票結果發送給Leader,只要半數以上返回了ACK信息就認爲經過,則執行 commit ,同時提交本身。再返回給Client。
假設Follwer宕機,是不會轉發到Leader,因此不會獲取到鎖。假設Leader宕機,就不會進行消息廣播,會先進行選取新Leader再處理,因此也不會丟失信息。算法

三. Redis分佈式鎖

單實例

Redis文檔中描述了單實例實現分佈式鎖的正確方法:sql

//添加鎖
SET resource_name my_random_value NX PX 30000
複製代碼
//釋放鎖
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
複製代碼

添加鎖時,key 的值是 my_random_value (一個隨機值),釋放鎖時先經過判斷當前 key 的值是否等於指定的值,這樣能夠避免誤刪,而且經過lua腳本實現原子性操做
若是不是原子性操做也會致使誤刪。del 命令若是因網絡等緣由延時,這時恰好 key 過時被另外一個客戶端獲取了共享資源,而後 del 命令又到達 redis 實例致使誤刪。數據庫

單實例有兩個缺點,1. 如何設定過時時間,2. 沒有高可用。apache

集羣

假設有一 Master 一 Slave ,當 Master 節點獲取到鎖以後,還沒同步到 Slave ,Master 就宕機發生主備切換,Slave 晉升爲 Master ,此時再申請鎖時,就會獲取到同一共享資源。服務器

對於這種狀況,redis的做者antirez提出了RedLock算法(但依然沒有解決設定過時時間的問題),獲取大多數節點的鎖就算加鎖成功,流程以下(來自官方文檔):
假設有N個Redis master(文檔假設有5個,大多數就是大於等於3),這些節點徹底互相獨立,不存在主從複製或者其餘集羣協調機制網絡

  1. 客戶端獲取當前時間(毫秒)
  2. 按順序使用相同的 key 和隨機值獲取全部N個實例中的鎖。在步驟2期間,當在每一個實例中設置鎖定時,客戶端須要設置一個響應超時時間,這個超時時間應該小於鎖的失效時間。 例如,若是自動釋放時間是10秒,則向每一個節點請求鎖的超時時間應該在5-50毫秒之間。這樣能夠避免服務器端Redis已經掛掉的狀況下,客戶端還在死死地等待響應結果。若是服務器端沒有在規定時間內響應,客戶端應該儘快嘗試另一個Redis實例。
  3. 客戶端計算步驟2所花費的總時間,減去開始獲取鎖的時間(步驟1記錄的時間)就獲得獲取鎖使用的時間。當且僅當從大多數(這裏是3個節點)的Redis節點都取到鎖,而且使用的時間小於鎖失效時間時,鎖纔算獲取成功。
  4. 若是取到了鎖,key的真正有效時間等於有效時間減去獲取鎖所使用的時間(步驟3計算的結果)。
  5. 若是由於某些緣由,獲取鎖失敗(沒有在至少N/2+1個Redis實例獲取到鎖或者獲取鎖時間已經超過了有效時間),客戶端應該在全部的Redis實例上進行解鎖(即使某些Redis實例根本就沒有加鎖成功)。

崩潰恢復 假設有5個節點,如今客戶端成功向3個節點加鎖,由於大於等於N/2+1個Redis實例,因此加鎖成功。這是3個節點中某一個節點宕機重啓,則其餘客戶端有機會向重啓節點和另外兩個節點加鎖。致使共享資源被屢次操做。
對於該狀況,做者提出了延遲重啓的方案,即當一個Redis 節點重啓後,只要它不參與到任意當前活動的鎖,爲了達到這種效果,咱們只須要將新的 Redis 實例,在一個TTL時間內,對客戶端不可用便可,在這個時間內,全部客戶端鎖將被失效或者自動釋放。dom

4、對比

  1. Redis 的讀寫性能確定比 zookeeper 高。zookeeper 可靠性高。
  2. zookeeper 的 watch 機制,能夠通知到下一個操做共享資源的客戶端。這樣客戶端能夠一直等待,直到收到通知再去獲取資源鎖。而 Redis 沒有通知機制。

[1] zookeeper.apache.org/doc/current…
[2] Distributed locks with Redis

相關文章
相關標籤/搜索