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

zookeeper實現分散式鎖

zookeeper介紹

一種提供配置管理分散式協同以及命名中心化服務

Zookeeper提供一個多層級的節點名稱空間(節點稱為znode),
每個節點都用一個以斜槓(/)分隔的路徑表示,而且每個節點都有父節點(根節點除外),非常類似於檔案系統。

例如,/foo/doo這個表示一個znode,它的父節點為/foo,父父節點為/,而/為根節點沒有父節點。

與檔案系統不同的是,這些節點都可以設定關聯的資料,而檔案系統中只有檔案節點可以存放資料而目錄節點不行。

Zookeeper為了保證高吞吐和低延遲,在記憶體中維護了這個樹狀的目錄結構,這種特性使得Zookeeper 不能用於存放大量的資料,每個節點的存放資料上限為1M

zookeeper叢集

zookeeper需要以叢集形態來部署,這樣只要叢集中大部分機器是可用的(能夠容忍一定的機器故障),那麼zookeeper本身仍然是可用的。客戶端在使用zookeeper時,需要知道叢集機器列表,通過與叢集中的某一臺機器建立TCP連線來使用服務,客戶端使用這個TCP連結來發送請求、獲取結果、獲取監聽事件以及傳送心跳包。如果這個連線異常斷開了,客戶端可以連線到另外的機器上。

在這裡插入圖片描述

  • 讀請求

可以被叢集中的任意一臺機器處理,如果讀請求在節點上註冊了監聽器,這個監聽器也是由所連線的zookeeper機器來處理。

  • 寫請求

這些請求會同時發給其他zookeeper機器並且達成一致後,請求才會返回成功。因此,隨著zookeeper的叢集機器增多,讀請求的吞吐會提高但是寫請求的吞吐會下降。

zookeeper性質

  • 有序節點

假如當前有一個父節點為/lock,我們可以在這個父節點下面建立子節點;zookeeper提供了一個可選的有序特性,例如我們可以建立子節點“/lock/node-”並且指明有序,那麼zookeeper在生成子節點時會根據當前的子節點數量自動新增整數序號,也就是說如果是第一個建立的子節點,那麼生成的子節點為/lock/node-0000000000,下一個節點則為/lock/node-0000000001,依次類推。

  • 臨時節點

客戶端可以建立一個臨時節點,在會話結束或者會話超時後,zookeeper會自動刪除該節點。

  • 事件監聽

在讀取資料時,我們可以同時對節點設定事件監聽,當節點資料或結構變化時,zookeeper會通知客戶端。當前zookeeper有如下四種事件:1)節點建立;2)節點刪除;3)節點資料修改;4)子節點變更。
zookeeper提供的API中設定監聽器的操作與讀操作是原子執行

的,也就是說在讀子節點列表時同時設定監聽器,保證不會丟失事件。

分散式鎖實現

  • 客戶端連線zookeeper,並在/lock下建立臨時的且有序的臨時子節點,第一個客戶端對應的子節點為/lock/lock-0000000000,第二個為/lock/lock-0000000001,以此類推。
  • 客戶端獲取/lock下的子節點列表,判斷自己建立的子節點是否為當前子節點列表中序號最小的子節點,如果是則認為獲得鎖,否則監聽/lock的子節點變更訊息,獲得子節點變更通知後重復此步驟直至獲得鎖;
  • 執行業務程式碼;
  • 完成業務流程後,刪除對應的子節點釋放鎖。

問題

  • 問題1:

步驟1中建立的臨時節點能夠保證在故障的情況下鎖也能被釋放,考慮這麼個場景:假如客戶端a當前建立的子節點為序號最小的節點,獲得鎖之後客戶端所在機器宕機了,客戶端沒有主動刪除子節點;如果建立的是永久的節點,那麼這個鎖永遠不會釋放,導致死鎖;由於建立的是臨時節點,客戶端宕機後,過了一定時間zookeeper沒有收到客戶端的心跳包判斷會話失效,將臨時節點刪除從而釋放鎖。

  • 問題2:

步驟2中獲取子節點列表與設定監聽這兩步操作的原子性問題,考慮這麼個場景:客戶端a對應子節點為/lock/lock-0000000000,客戶端b對應子節點為/lock/lock-0000000001,客戶端b獲取子節點列表時發現自己不是序號最小的,但是在設定監聽器前客戶端a完成業務流程刪除了子節點/lock/lock-0000000000,客戶端b設定的監聽器豈不是丟失了這個事件從而導致永遠等待了?這個問題不存在的。因為zookeeper提供的API中設定監聽器的操作與讀操作是原子執行的,也就是說在讀子節點列表時同時設定監聽器,保證不會丟失事件

  • 問題3:

假如當前有1000個節點在等待鎖,如果獲得鎖的客戶端釋放鎖時,這1000個客戶端都會被喚醒,這種情況稱為**“羊群效應”**;在這種羊群效應中,zookeeper需要通知1000個客戶端,這會阻塞其他的操作,最好的情況應該只喚醒新的最小節點對應的客戶端。應該怎麼做呢?在設定事件監聽時,每個客戶端應該對剛好在它之前的子節點設定事件監聽,例如子節點列表為/lock/lock-0000000000、/lock/lock-0000000001、/lock/lock-0000000002,序號為1的客戶端監聽序號為0的子節點刪除訊息序號為2的監聽序號為1的子節點刪除訊息

優化

  • 客戶端連線zookeeper,並在/lock下建立臨時的且有序的子節點,第一個客戶端對應的子節點為/lock/lock-0000000000,第二個為/lock/lock-0000000001,以此類推。
  • 客戶端獲取/lock下的子節點列表,判斷自己建立的子節點是否為當前子節點列表中序號最小的子節點,如果是則認為獲得鎖,否則監聽剛好在自己之前一位的子節點刪除訊息,獲得子節點變更通知後重復此步驟直至獲得鎖;
  • 執行業務程式碼;
  • 完成業務流程後,刪除對應的子節點釋放鎖。

開源框架Curator

可以直接使用curator這個開源專案提供的zookeeper分散式鎖實現