1. 程式人生 > >zookeeper分散式鎖實現原理

zookeeper分散式鎖實現原理

1、互斥鎖mutex lock

顧名思義就是排它鎖,同一時間只允許一個客戶端執行。

實現步驟:

  • 首先,建立一個lock node,例如“locknode
  • 其次,客戶端lock執行以下方式: 
    1. 建立(create)一個有序臨時節點,例如“locknode/guid-lock-”,其中guid可以是你客戶端的唯一識別序號,如果發生前面說的建立失敗問題,需要使用guid進行手動檢查。
    2. 呼叫getChildren(watch=false)獲取獲取子節點列表,注意wtach設定為false,以避免羊群效應(Herd Effect),即同時收到太多無效節點刪除通知。
    3. 從這個列表中,判斷自己建立的節點序號是否是最小
      ,如果是則直接返回true,否則繼續往下走。
    4. 從步驟2中獲取的list中選取排在當前節點前一位的節點,呼叫exist(watch=true)方法。
    5. 如果exist返回false,則回到步驟2;
    6. 如果exist返回true,則等待exist的哨兵(watch)回撥通知,收到通知後再執行步驟2.
  • 最後,客戶端unlock只需要呼叫delete刪除掉節點即可。

節點操作示意圖:

這裡寫圖片描述

流程圖:

這裡寫圖片描述

優點

  • 避免了輪詢和超時控制
  • 每次一個子節點的刪除動作,只會觸發唯一一個客戶端的watch動作,從而避免了羊群效應
  • 便於觀測

缺點

  • 沒有解決鎖重入問題,因為採用的是有序臨時節點,因此多次呼叫create並不會觸發KeeperException.NodeExists異常,從而無法實現鎖重入功能。如果需要解決,則在步驟1
    時,需要先進行判斷當前節點是否已經存在,即呼叫getChildren(watch=false),判斷當前節點是否已經建立(配合guid),已經建立,則直接從步驟3開始,沒有建立則從步驟1開始。
  • 這是一個公平鎖,無法實現非公平鎖。參考[4]實現了一個非公平鎖

注意:

如果一個節點建立了一個sequential ephemeral nodes,但是在server返回建立成功的時候,server掛了,此時客戶端需要重新連線,重新連線後會話依然有效,但其建立的臨時節點卻沒有刪除。解決方式就是在每次建立時create,如果發生失敗,客戶端需要getChildren(),進行手動檢查是否獲取鎖,這個時候就需要使用guid。

2、共享鎖Shared Locks或讀寫鎖Read/Write Locks

Read讀鎖是共享鎖,Write寫鎖是排它鎖,當沒有寫時,允許多個read例項獲取讀鎖,當有一個write例項獲得寫鎖時,則不允許任何其他write例項和read例項獲得鎖。

實現步驟:

  • 首先,建立一個lock node,例如“locknode
  • 獲取read鎖步驟: 
    1. 建立(create)一個有序臨時節點,例如“locknode/read-guid-lock-”,其中guid可以是你客戶端的唯一識別序號,如果發生前面說的建立失敗問題,需要使用guid進行手動檢查。
    2. 呼叫getChildren(watch=false)獲取獲取子節點列表,注意wtach設定為false,以避免羊群效應(Herd Effect),即同時收到太多無效節點刪除通知。
    3. 從這個列表中,判斷是否有序號比自己小、且路徑名以“write-”開頭的節點,如果沒有,則直接獲取讀鎖,否則繼續如下步驟。
    4. 從步驟2中獲取的list中選取排在當前節點前一位的、且路徑名以“write-”開頭的節點,呼叫exist(watch=true)方法。
    5. 如果exist返回false,則回到步驟2。
    6. 如果exist返回true,則等待exist的哨兵(watch)回撥通知,收到通知後再執行步驟2。
  • 獲取write鎖步驟: 
    1. 建立(create)一個有序臨時節點,例如“locknode/write-guid-lock-”,其中guid可以是你客戶端的唯一識別序號,如果發生前面說的建立失敗問題,需要使用guid進行手動檢查。
    2. 呼叫getChildren(watch=false)獲取獲取子節點列表,注意wtach設定為false,以避免羊群效應(Herd Effect),即同時收到太多無效節點刪除通知。
    3. 從這個列表中,判斷自己建立的節點序號是否是最小,如果是則直接返回true,否則繼續往下走。
    4. 從步驟2中獲取的list中選取排在當前節點前一位的節點,呼叫exist(watch=true)方法。
    5. 如果exist返回false,則回到步驟2;
    6. 如果exist返回true,則等待exist的哨兵(watch)回撥通知,收到通知後再執行步驟2.
  • 最後,客戶端unlock只需要呼叫delete刪除掉節點即可。

節點操作示意圖:

這裡寫圖片描述

流程圖:

  • read lock

這裡寫圖片描述

  • write lock

這裡寫圖片描述

優點

  • 避免了輪詢和超時控制
  • 每次一個子節點的刪除動作,只會觸發唯一一個客戶端的watch動作,從而避免了羊群效應
  • 便於觀測

缺點

  • 沒有解決鎖重入問題,因為採用的是有序臨時節點,因此多次呼叫create並不會觸發KeeperException.NodeExists異常,從而無法實現鎖重入功能。如果需要解決,則在步驟1時,需要先進行判斷當前節點是否已經存在,即呼叫getChildren(watch=false),判斷當前節點是否已經建立(配合guid),已經建立,則直接從步驟3開始,沒有建立則從步驟1開始。
  • 當有非常多的read節點在等待一個write節點刪除通知時,一旦write節點刪除,將會觸發非常多的read節點被呼叫,不過這種情況無法避免。

可撤銷和超時問題

當前的讀寫鎖並沒有考慮讀鎖可撤銷和超時問題,如何讓讀鎖主動放棄,如何判斷超時等,我想可行的方案還是在客戶端自己處理,如果其他客戶端想讓前面的節點放棄鎖,可以在節點寫入unlock資訊,讓持有鎖的客戶端監聽該變化,收到unlock資訊,自己主動放棄對鎖的持有。

3、參考