Zookeeper分布式鎖服務及總結

本篇分享文章,是對分佈式鎖服務的階段性總結,大家可以去前面翻看相關文章,希望能給讀者帶來收穫。

今天的目錄如下:

  1. 分佈式鎖功能回顧

  2. 分佈式鎖實現思路

  3. zookeeper的簡單介紹

  4. 實現分佈式鎖服務

1. 分佈式鎖功能回顧

前面我們已經分析過分佈式鎖服務的功能,大家有興趣去看看《分佈式鎖服務的思考》,總結一下功能點:

  • 鎖定服務的接口或資源,防止多個線程同時執行

  • 分佈式鎖服務本身需要具有高可用性

  • 可靠的獲得鎖以及釋放鎖

  • 鎖具有超時機制,不會發生死鎖,或者具有死鎖檢測機制

  • 同一個線程可以重複鎖定資源,分佈式鎖具有可重入的特點

  • 客戶端獲得鎖具有超時機制或重試機制。

2. 分佈式鎖實現思路

分佈式鎖服務實現方式有多種,其中比較流行的分佈式鎖實現有如下:

  • 結合數據庫事務實現,只要數據庫架構高可用,基本上能滿足大多數的鎖服務要求。但是需要重點考慮線程阻塞,且鎖沒有超時過期的特性。

  • 使用Redis實現。注意,我在前面的文章《Redis紅鎖不適合分佈式鎖服務》,Redis紅鎖並不適合做分佈式鎖服務,原因也分析了:5個節點太重,且不能解決線程的阻塞導致多個線程持有鎖。還不如直接使用單機版本的setnx + lua(delete key) 這種方式來的乾脆。(不管單機版的分佈式鎖還是紅鎖,都具有線程阻塞問題,詳細說明見前面的兩篇文章)

  • zookeeper實現分佈式鎖,今天我們重點說明zookeeper以及實現分佈式鎖。

數據庫利用事務實現分佈式鎖服務比較簡單,不在這裡深入了。

Redis實現分佈式鎖服務請看前面兩篇文章,不建議使用紅鎖。

下面開始我們今天的重點,使用zookeeper實現鎖服務。(zk上年紀了,但它非常經典,有必要學習一下)

3. zookeeper的簡單介紹

Zookeeper分佈式鎖服務及總結

服務間數據同步以及選舉

比較重要的特點:

  1. 服務之間相互通信,在內存中維持了服務的狀態圖以及事務日誌。

  2. 快照存儲在硬盤中,多數可用,服務就可用。

  3. 因為存儲使用內存,速度快,官網解釋10比1的讀寫性能最佳。

  4. 事務順序數字標記,具有順序操作特性,原子性操作。

  5. 有層級命名空間,很像是分佈式文件系統。唯一的區別就是每一個命名空間下的node節點以及它的children節點都可以關聯數據。這就像電腦的文件系統,一個文件即存儲數據,也可以作為一個目錄。

  6. 節點分為永久節點和臨時節點,臨時節點生命同client的session一致,臨時節點不允許有孩子。

  7. watch機制,監聽節點的變化,一旦觸發通知,watch就消失了,若想繼續監聽則必須重新set watch。

Zookeeper分佈式鎖服務及總結

數據節點模型

zookeeper常用的場景:

  • 統一命名空間

    dubbo服務提供者把自己的服務Url地址註冊到,如/dubbo/${serviceName}/providers/目錄下,完成服務的發佈操作。dubbo服務消費者啟動後,訂閱/dubbo/${serviceName}/providers目錄下的Url地址,並向/dubbo/${serviceName}/consumer/中寫下自己的Url。所有的地址都是臨時的節點,這樣服務的提供者和消費者都能感知到資源的變化,dubbo可以監控/dobbo/${serviceName}/下面的所有服務者和消費者的信息。

  • 配置管理

    利用zk實現配置中心,在應用時首先要封裝一套應用層的API方便業務邏輯使用,如果我們的服務比較少使用文件就可以滿足,反之需要使用配置中心管理服務,例如實現服務降級開關。除了zookeeper,還有Etcd3、consul等,這裡可告訴大家,zk實現配置管理比較重,管理複雜,不建議使用了。

  • 分佈式集群管理

    zookeeper很早就出現了,是大數據hadoop生態圈的協調管理者。zk集群選舉機制採用了zab協議,保證了分佈式數據的一致性,並實現leader選舉。

  • 分佈式鎖服務(強一致性)

    保持資源獨佔和嚴格的控制時序。使用一致性保證,可以很簡單的構建一些高級的用法:客戶端barriers、queues、以及可撤銷的讀寫鎖。

4. 實現分佈式鎖服務

使用zk實現分佈式鎖很簡單,首先我們看下client獲得鎖的步驟:

  1. 參與鎖競爭的client調用create()方法創建一個路徑比如:‘locknode/lock-0000001’的有序臨時節點。

  2. 在locknode節點上調用getChildren(),獲得有序節點列表,為防止羊群效應,這裡不可設置watch。

  3. 如果client在第一步創建的是列表中最小的後綴節點,那麼該client獲得鎖並返回。

  4. 如果創建的節點序列,不是列表中最小的節點,則client調用exists()監聽該節點的上一個節點。

  5. 返回null,重新返回step2執行。否則,等待通知獲得鎖的擁有權。

釋放鎖的流程很簡單:只需要刪除自己創建的節點。zk實現的分佈式鎖沒有輪詢,所以等待通知就好。

下面用代碼說明上述過程:

1.在目錄下創建臨時有序節點的過程

Zookeeper分佈式鎖服務及總結

創建臨時節點,注意失敗重試

2.創建目錄之後,可以直接獲得臨時節點名稱。這裡看下demo通過session獲得節點的方法

Zookeeper分佈式鎖服務及總結

通過session前綴得到臨時節點名字

Zookeeper分佈式鎖服務及總結

封裝的對象

為了方便臨時節點的表示,demo給出一個封裝對象:ZNodeName。實現compare方法比較大小,方便排序

3.獲取lock(dir)目錄下的所有children,封裝ZNodeName對象並放入Set中

Zookeeper分佈式鎖服務及總結

獲取dir目錄下的所有臨時節點,放入Set中

4.比較當前client是否是最小的節點

lessThanMe.isEmpty() //表示當前session創建的節點具有最小的suffix

Zookeeper分佈式鎖服務及總結

獲得鎖返回成功

5.當前節點並非最小值,就監聽當前臨時節點的上一個臨時節點。例如當前節點是003,那麼就監聽002。

Zookeeper分佈式鎖服務及總結

監聽上一個節點的變化

等待上一個節點的釋放觸發LockWatcher,再去執行一遍獲得鎖的邏輯。

如果上一個節點不存在,本次獲得鎖失敗。

Zookeeper分佈式鎖服務及總結

LockWatcher的邏輯

以上代碼呢,大家只要能看懂大概zk的實現方式即可,為什麼只是瞭解呢?

  • 首先zookeeper開發比較重,就是不方便。例如,簡單的創建zk對象,就需要阻塞等待通知。

  • 另外,我們在應用中可直接使用apache封裝好的工具curator就可以了,使用簡單,學習成本低。

    • acquire

    • release

5. 總結

  • 在分佈式應用中,需要防止併發訪問資源造成數據不一致,需可重入分佈式鎖。

  • 常用的分佈式鎖實現。mysql默認Repeatable Read事務隔離以及Redis setnx +lua delete。

  • 簡單介紹zk,瞭解即可,不需要深入了。

  • 代碼介紹原理,瞭解接即可,因為直接使用curator更方便

希望本文能給大家帶來收穫,如果有疑問或者覺得表述不正確,可在評論中指出。


分享到:


相關文章: