首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Apache Curator 實作 ZooKeeper 分散式鎖:InterProcessMutex 完整範例

     編輯:Java 程式設計入門

前言

在分散式系統中,協調多節點對共享資源的互斥存取是核心課題。ZooKeeper 提供了順序節點、臨時節點、Watcher 機制等原語,可構建分散式鎖,但直接操作底層 API 複雜且易出錯(羊群效應、連線斷開重試、鎖釋放洩漏)。Apache Curator 封裝了成熟的 InterProcessMutex(可重入排他鎖)與 InterProcessSemaphoreMutex(不可重入),並內建連線狀態管理、自動重試、鎖釋放保護,是生產環境首選。

核心類別與流程

CuratorFramework (連線管理) 
    → RetryPolicy (重試策略) 
    → InterProcessMutex (鎖實作) 
    → acquire() / release()

完整可執行範例

import java.util.concurrent.*;
import com.netflix.curator.RetryPolicy;
import com.netflix.curator.framework.*;
import com.netflix.curator.framework.recipes.locks.InterProcessMutex;
import com.netflix.curator.retry.ExponentialBackoffRetry;

public class CuratorDistributedLockDemo {

    private static final String ZK_CONNECTION_STRING = "localhost:2181,localhost:2182,localhost:2183";
    private static final String LOCK_PATH = "/shared-locks/resource-a";
    private static final int CLIENT_COUNT = 5;
    private static final int HOLD_TIME_SECONDS = 10;

    public static void main(String[] args) throws Exception {
        // 1. 建立 Curator 客戶端(單例模式,全應用共用)
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); // 基礎睡眠 1s,最多重試 3 次
        CuratorFramework client = CuratorFrameworkFactory.newClient(ZK_CONNECTION_STRING, retryPolicy);
        client.start();
        System.out.println("Curator 客戶端已啟動");

        // 2. 執行緒池模擬多客戶端競爭
        ExecutorService executor = Executors.newFixedThreadPool(CLIENT_COUNT);
        CountDownLatch latch = new CountDownLatch(CLIENT_COUNT);

        for (int i = 0; i < CLIENT_COUNT; i++) {
            final String clientName = "Client-" + i;
            executor.submit(() -> {
                try {
                    // 每個任務建立自己的鎖實例(共享同一 ZK 路徑)
                    InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);
                    
                    // 嘗試獲取鎖,最多等待 120 秒
                    if (lock.acquire(HOLD_TIME_SECONDS, TimeUnit.SECONDS)) {
                        try {
                            System.out.println("[" + clientName + "] 成功獲取鎖,開始處理業務...");
                            Thread.sleep(HOLD_TIME_SECONDS * 1000L); // 模擬業務處理
                            System.out.println("[" + clientName + "] 業務處理完成");
                        } finally {
                            // 必須在 finally 釋放,防止異常導致死鎖
                            lock.release();
                            System.out.println("[" + clientName + "] 已釋放鎖");
                        }
                    } else {
                        System.err.println("[" + clientName + "] 獲取鎖超時");
                    }
                } catch (Exception e) {
                    System.err.println("[" + clientName + "] 發生異常: " + e.getMessage());
                } finally {
                    latch.countDown();
                }
            });
        }

        // 3. 等待所有任務完成
        latch.await();
        executor.shutdown();
        client.close();
        System.out.println("所有任務完成,客戶端已關閉");
    }
}

關鍵配置說明

重試策略選擇

策略適用場景參數範例
ExponentialBackoffRetry通用生產環境,避免雪崩baseSleep=1000ms, maxRetries=3
RetryNTimes固定間隔、固定次數n=5, sleepMs=2000
RetryOneTime快速失敗、非關鍵路徑sleepMs=3000
RetryUntilElapsed截止時間前持續重試maxElapsedTimeMs=30000

鎖路徑設計建議

  • 按資源粒度劃分:/locks/order-service/create-order、/locks/inventory/deduct
  • 避免單一路徑過度競爭,必要時拆分為多把細粒度鎖
  • 路徑需預先建立(或啟用 client.create().creatingParentsIfNeeded())

進階主題

1. 可重入特性驗證

InterProcessMutex 內部使用執行緒 ID + 計數器實現可重入,同一執行緒多次 acquire() 只會在 ZK 建立一個順序節點,release() 計數歸零才真正刪除節點:

InterProcessMutex lock = new InterProcessMutex(client, "/locks/test");
lock.acquire(); // 第 1 次
lock.acquire(); // 第 2 次,不阻塞
lock.release(); // 計數 2→1
lock.release(); // 計數 1→0,真正釋放

2. 鎖超時與租約機制

  • acquire(timeout, unit):客戶端等待上限,超時返回 false
  • ZK 端無「鎖自動過期」機制,鎖持有者若崩潰(Session 過期),臨時節點自動刪除,次順序節點自動獲得鎖
  • 建議設定 sessionTimeoutMs(如 30s),平衡故障偵測速度與網路抖動容忍度

3. 羊群效應緩解

Curator 使用「順序節點 + Watcher 鏈」:釋放鎖時只通知下一個節點,而非廣播所有等待者,避免大量客戶端同時醒來衝擊 ZK。

4. 監控與診斷

// 註冊連線狀態監聽
client.getConnectionStateListenable().addListener((client, newState) -> {
    System.out.println("ZK 連線狀態變更: " + newState); // CONNECTED, SUSPENDED, LOST, RECONNECTED
});

// 獲取鎖統計資訊(需 Curator 4.2+)
InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);
System.out.println("參與排隊節點數: " + lock.getParticipants().size());
System.out.println("當前持有者: " + lock.getParticipantNodes());

常見坑點與最佳實踐

  • 務必在 finally 釋放鎖:任何業務異常都不能導致鎖洩漏。
  • 不要在鎖內做長時間 I/O 或 RPC:會拉長鎖持有時間,增加競爭延遲。若業務耗時長,考慮改用樂觀鎖或分散式事務(Saga)。
  • 客戶端單例:CuratorFramework 昂貴且執行緒安全,全應用僅建立一個實例。
  • 關閉順序:先 executor.shutdown() 等任務結束,再 client.close(),避免任務還在跑時連線被切斷。
  • 測試環境單機 ZK:開發階段可用 TestingServer 嵌入式啟動,無需外部依賴。

總結

Curator 的 InterProcessMutex 以 約 50 行核心程式碼 解決了分散式鎖的所有棘手問題:連線管理、重試、可重入、羊群效應、故障自動恢復。掌握其配置細節(重試策略、Session Timeout、鎖路徑設計),即可在生產環境安心使用。若需共享鎖(讀寫分離),可進一步研究 InterProcessReadWriteLock。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved