在分散式系統中,協調多節點對共享資源的互斥存取是核心課題。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/deductclient.create().creatingParentsIfNeeded())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,真正釋放
acquire(timeout, unit):客戶端等待上限,超時返回 falsesessionTimeoutMs(如 30s),平衡故障偵測速度與網路抖動容忍度Curator 使用「順序節點 + Watcher 鏈」:釋放鎖時只通知下一個節點,而非廣播所有等待者,避免大量客戶端同時醒來衝擊 ZK。
// 註冊連線狀態監聽
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());
CuratorFramework 昂貴且執行緒安全,全應用僅建立一個實例。executor.shutdown() 等任務結束,再 client.close(),避免任務還在跑時連線被切斷。TestingServer 嵌入式啟動,無需外部依賴。Curator 的 InterProcessMutex 以 約 50 行核心程式碼 解決了分散式鎖的所有棘手問題:連線管理、重試、可重入、羊群效應、故障自動恢復。掌握其配置細節(重試策略、Session Timeout、鎖路徑設計),即可在生產環境安心使用。若需共享鎖(讀寫分離),可進一步研究 InterProcessReadWriteLock。