zookeeper分散式鎖實現原理
阿新 • • 發佈:2019-02-05
1、互斥鎖mutex lock
顧名思義就是排它鎖,同一時間只允許一個客戶端執行。
實現步驟:
- 首先,建立一個lock node,例如“locknode”
- 其次,客戶端lock執行以下方式:
- 建立(create)一個有序臨時節點,例如“locknode/guid-lock-”,其中guid可以是你客戶端的唯一識別序號,如果發生前面說的建立失敗問題,需要使用guid進行手動檢查。
- 呼叫getChildren(watch=false)獲取獲取子節點列表,注意wtach設定為false,以避免羊群效應(Herd Effect),即同時收到太多無效節點刪除通知。
- 從這個列表中,判斷自己建立的節點序號是否是最小
- 從步驟2中獲取的list中選取排在當前節點前一位的節點,呼叫exist(watch=true)方法。
- 如果exist返回false,則回到步驟2;
- 如果exist返回true,則等待exist的哨兵(watch)回撥通知,收到通知後再執行步驟2.
- 最後,客戶端unlock只需要呼叫delete刪除掉節點即可。
節點操作示意圖:
流程圖:
優點:
- 避免了輪詢和超時控制
- 每次一個子節點的刪除動作,只會觸發唯一一個客戶端的watch動作,從而避免了羊群效應
- 便於觀測
缺點:
- 沒有解決鎖重入問題,因為採用的是有序臨時節點,因此多次呼叫create並不會觸發KeeperException.NodeExists異常,從而無法實現鎖重入功能。如果需要解決,則在步驟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鎖步驟:
- 建立(create)一個有序臨時節點,例如“locknode/read-guid-lock-”,其中guid可以是你客戶端的唯一識別序號,如果發生前面說的建立失敗問題,需要使用guid進行手動檢查。
- 呼叫getChildren(watch=false)獲取獲取子節點列表,注意wtach設定為false,以避免羊群效應(Herd Effect),即同時收到太多無效節點刪除通知。
- 從這個列表中,判斷是否有序號比自己小、且路徑名以“write-”開頭的節點,如果沒有,則直接獲取讀鎖,否則繼續如下步驟。
- 從步驟2中獲取的list中選取排在當前節點前一位的、且路徑名以“write-”開頭的節點,呼叫exist(watch=true)方法。
- 如果exist返回false,則回到步驟2。
- 如果exist返回true,則等待exist的哨兵(watch)回撥通知,收到通知後再執行步驟2。
- 獲取write鎖步驟:
- 建立(create)一個有序臨時節點,例如“locknode/write-guid-lock-”,其中guid可以是你客戶端的唯一識別序號,如果發生前面說的建立失敗問題,需要使用guid進行手動檢查。
- 呼叫getChildren(watch=false)獲取獲取子節點列表,注意wtach設定為false,以避免羊群效應(Herd Effect),即同時收到太多無效節點刪除通知。
- 從這個列表中,判斷自己建立的節點序號是否是最小,如果是則直接返回true,否則繼續往下走。
- 從步驟2中獲取的list中選取排在當前節點前一位的節點,呼叫exist(watch=true)方法。
- 如果exist返回false,則回到步驟2;
- 如果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、參考