看這篇博客之前可以先去了解博主的另一篇講解ZooKeeper的博客:分布式微服務--ZooKeeper的客戶端常用命令 & Java API 操作-CSDN博客
1. 為什么需要分布式鎖?
在分布式系統中,多個服務節點可能同時訪問或修改同一份共享資源(例如數據庫記錄、緩存數據、文件等)。
如果不加限制,容易出現數據不一致或競爭條件。
因此,需要一種機制來保證:同一時刻只有一個節點能訪問某資源。這就是分布式鎖的意義。常見分布式鎖實現方式:
基于 Redis
基于 ZooKeeper
基于 數據庫
其中 ZooKeeper 實現分布式鎖更安全可靠,因為它的核心是 強一致性 + 臨時順序節點機制。
2. ZooKeeper 的分布式鎖原理
ZooKeeper 提供的 臨時節點(Ephemeral) 和 有序節點(Sequential) 是實現分布式鎖的關鍵。
核心思想:
創建鎖節點:
所有客戶端到某個固定路徑(如/lock
)下創建一個 臨時順序節點,例如:/lock/lock_00000001 /lock/lock_00000002 /lock/lock_00000003
臨時節點保證客戶端宕機或斷開時自動刪除。
順序節點保證多個客戶端的排隊順序。
獲取鎖:
客戶端判斷自己創建的節點是否是 序號最小的節點。
如果是 → 獲取鎖成功。
如果不是 → 監聽比自己小的前一個節點,等待其釋放。
釋放鎖:
客戶端執行完業務邏輯后,刪除自己的節點 → 喚醒下一個等待者。
3. 分布式鎖實現步驟
假設我們要對某個共享資源
order
加鎖。1)獲取鎖
客戶端在
/lock/order
下創建臨時順序節點,例如:
/lock/order/lock_00000010
獲取
/lock/order
下所有子節點,并排序。如果自己是最小的節點 → 獲得鎖。
否則 → 監聽自己前一個節點。
2)釋放鎖
客戶端完成業務邏輯后,刪除自己的節點。
ZooKeeper 會通知下一個等待者。
3)異常情況
如果客戶端宕機或與 ZooKeeper 斷開連接,ZooKeeper 會自動刪除臨時節點,從而避免鎖“死鎖”。
4. ZooKeeper 分布式鎖代碼示例
基于 Curator(推薦)
Curator 是 ZooKeeper 的一個高級客戶端,封裝了分布式鎖。
![]()
樣例代碼(本次使用的鎖是InterProcessMutex):
xml
<!--curator--><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>4.0.0</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.0.0</version></dependency>
代碼
/*** 模擬12306售票系統 —— 使用 Zookeeper 分布式鎖保證并發安全*/ public class Ticket12306 implements Runnable{// 模擬數據庫中的票數private int tickets = 10;// 分布式可重入鎖對象(Curator 提供)private InterProcessMutex lock ;public Ticket12306(){// 1. 定義重試策略:初始等待時間 3 秒,最多重試 10 次RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);// 2. 通過 builder 模式創建 CuratorFramework 客戶端CuratorFramework client = CuratorFrameworkFactory.builder().connectString("127.0.0.1:2181") // Zookeeper 連接地址.sessionTimeoutMs(60 * 1000) // 會話超時時間.connectionTimeoutMs(15 * 1000) // 連接超時時間.retryPolicy(retryPolicy) // 指定重試策略.build();// 3. 開啟連接client.start();// 4. 創建分布式鎖對象,指定鎖的路徑(ZK 節點)// 不同客戶端只要路徑一樣,就能實現互斥lock = new InterProcessMutex(client,"/lock");}@Overridepublic void run() {while(true){try {// 1. 嘗試獲取鎖,最多等待 3 秒lock.acquire(3, TimeUnit.SECONDS);// 2. 拿到鎖后執行業務邏輯 —— 賣票if(tickets > 0){System.out.println(Thread.currentThread()+":" + tickets);Thread.sleep(100); // 模擬業務處理耗時tickets--; // 賣出一張票}} catch (Exception e) {e.printStackTrace();} finally {// 3. 無論如何,最后都要釋放鎖,避免死鎖try {lock.release();} catch (Exception e) {e.printStackTrace();}}}} }
測試:
/*** 測試類:模擬多個客戶端同時搶票*/ public class LockTest {public static void main(String[] args) {// 創建一個 Ticket12306 對象(共享 10 張票)Ticket12306 ticket12306 = new Ticket12306();// 創建兩個線程,模擬兩個不同的售票平臺(攜程、飛豬)Thread t1 = new Thread(ticket12306,"攜程");Thread t2 = new Thread(ticket12306,"飛豬");// 啟動線程,同時賣票t1.start();t2.start();} }
? 總結:
InterProcessMutex
是 Curator 提供的 可重入分布式鎖,底層用 Zookeeper 的臨時順序節點實現。
lock.acquire()
獲取鎖,獲取不到會阻塞(或超時返回false
)。
lock.release()
釋放鎖,必須寫在finally
,避免異常導致死鎖。這種方式可以模擬 多進程/多機器的并發安全,保證同一時刻只有一個客戶端在修改票數。
5. ZooKeeper 分布式鎖的優缺點
? 優點
強一致性:ZK 的數據一致性保證了鎖的可靠性。
自動釋放:客戶端宕機,臨時節點會自動刪除,避免死鎖。
公平性:順序節點機制,保證先來先服務。
? 缺點
性能較低:每次加鎖/解鎖都需要與 ZK 通信,適合低并發場景。
部署復雜:需要搭建 ZooKeeper 集群。
羊群效應:節點刪除時可能觸發大量客戶端通知(不過監聽前一個節點可以緩解)。
6. 使用場景
訂單系統(防止超賣)
分布式定時任務(同一時間只允許一個節點執行)
共享資源訪問控制(文件、緩存等)
7. 總結
ZooKeeper 分布式鎖依賴 臨時順序節點 + watcher 機制。
Curator 封裝了常用的鎖(
InterProcessMutex
),開發更方便。適合一致性要求高、并發量不是極高的業務場景。
高并發下更推薦 Redis 分布式鎖(性能更好,但需要妥善解決可靠性問題)。