本篇分享文章,是對分佈式鎖服務的階段性總結,大家可以去前面翻看相關文章,希望能給讀者帶來收穫。
今天的目錄如下:
分佈式鎖功能回顧
分佈式鎖實現思路
zookeeper的簡單介紹
實現分佈式鎖服務
1. 分佈式鎖功能回顧
前面我們已經分析過分佈式鎖服務的功能,大家有興趣去看看《分佈式鎖服務的思考》,總結一下功能點:
鎖定服務的接口或資源,防止多個線程同時執行
分佈式鎖服務本身需要具有高可用性
可靠的獲得鎖以及釋放鎖
鎖具有超時機制,不會發生死鎖,或者具有死鎖檢測機制
同一個線程可以重複鎖定資源,分佈式鎖具有可重入的特點
客戶端獲得鎖具有超時機制或重試機制。
2. 分佈式鎖實現思路
分佈式鎖服務實現方式有多種,其中比較流行的分佈式鎖實現有如下:
結合數據庫事務實現,只要數據庫架構高可用,基本上能滿足大多數的鎖服務要求。但是需要重點考慮線程阻塞,且鎖沒有超時過期的特性。
使用Redis實現。注意,我在前面的文章《Redis紅鎖不適合分佈式鎖服務》,Redis紅鎖並不適合做分佈式鎖服務,原因也分析了:5個節點太重,且不能解決線程的阻塞導致多個線程持有鎖。還不如直接使用單機版本的setnx + lua(delete key) 這種方式來的乾脆。(不管單機版的分佈式鎖還是紅鎖,都具有線程阻塞問題,詳細說明見前面的兩篇文章)
zookeeper實現分佈式鎖,今天我們重點說明zookeeper以及實現分佈式鎖。
數據庫利用事務實現分佈式鎖服務比較簡單,不在這裡深入了。
Redis實現分佈式鎖服務請看前面兩篇文章,不建議使用紅鎖。
下面開始我們今天的重點,使用zookeeper實現鎖服務。(zk上年紀了,但它非常經典,有必要學習一下)
3. zookeeper的簡單介紹
比較重要的特點:
服務之間相互通信,在內存中維持了服務的狀態圖以及事務日誌。
快照存儲在硬盤中,多數可用,服務就可用。
因為存儲使用內存,速度快,官網解釋10比1的讀寫性能最佳。
事務順序數字標記,具有順序操作特性,原子性操作。
有層級命名空間,很像是分佈式文件系統。唯一的區別就是每一個命名空間下的node節點以及它的children節點都可以關聯數據。這就像電腦的文件系統,一個文件即存儲數據,也可以作為一個目錄。
-
節點分為永久節點和臨時節點,臨時節點生命同client的session一致,臨時節點不允許有孩子。
watch機制,監聽節點的變化,一旦觸發通知,watch就消失了,若想繼續監聽則必須重新set watch。
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獲得鎖的步驟:
參與鎖競爭的client調用create()方法創建一個路徑比如:‘locknode/lock-0000001’的有序臨時節點。
在locknode節點上調用getChildren(),獲得有序節點列表,為防止羊群效應,這裡不可設置watch。
如果client在第一步創建的是列表中最小的後綴節點,那麼該client獲得鎖並返回。
如果創建的節點序列,不是列表中最小的節點,則client調用exists()監聽該節點的上一個節點。
返回null,重新返回step2執行。否則,等待通知獲得鎖的擁有權。
釋放鎖的流程很簡單:只需要刪除自己創建的節點。zk實現的分佈式鎖沒有輪詢,所以等待通知就好。
下面用代碼說明上述過程:
1.在目錄下創建臨時有序節點的過程
2.創建目錄之後,可以直接獲得臨時節點名稱。這裡看下demo通過session獲得節點的方法
為了方便臨時節點的表示,demo給出一個封裝對象:ZNodeName。實現compare方法比較大小,方便排序
3.獲取lock(dir)目錄下的所有children,封裝ZNodeName對象並放入Set中
4.比較當前client是否是最小的節點
lessThanMe.isEmpty() //表示當前session創建的節點具有最小的suffix
5.當前節點並非最小值,就監聽當前臨時節點的上一個臨時節點。例如當前節點是003,那麼就監聽002。
等待上一個節點的釋放觸發LockWatcher,再去執行一遍獲得鎖的邏輯。
如果上一個節點不存在,本次獲得鎖失敗。
以上代碼呢,大家只要能看懂大概zk的實現方式即可,為什麼只是瞭解呢?
首先zookeeper開發比較重,就是不方便。例如,簡單的創建zk對象,就需要阻塞等待通知。
另外,我們在應用中可直接使用apache封裝好的工具curator就可以了,使用簡單,學習成本低。
acquire
release
5. 總結
在分佈式應用中,需要防止併發訪問資源造成數據不一致,需可重入分佈式鎖。
常用的分佈式鎖實現。mysql默認Repeatable Read事務隔離以及Redis setnx +lua delete。
簡單介紹zk,瞭解即可,不需要深入了。
代碼介紹原理,瞭解接即可,因為直接使用curator更方便
希望本文能給大家帶來收穫,如果有疑問或者覺得表述不正確,可在評論中指出。
閱讀更多 吳濤分享 的文章