Apache ZooKeeper 是分散式協調服務的標準組件,提供配置管理、命名服務、分散式鎖、叢集管理等核心能力。本文從安裝部署到核心應用場景實現,提供完整入門指南。
zookeeper-cluster/
├── server1/
│ ├── data/
│ ├── dataLog/
│ ├── logs/
│ └── apache-zookeeper-3.7.0-bin/
├── server2/
│ ├── data/
│ ├── dataLog/
│ ├── logs/
│ └── apache-zookeeper-3.7.0-bin/
└── server3/
├── data/
├── dataLog/
├── logs/
└── apache-zookeeper-3.7.0-bin/
每個 server data 目錄建立 myid:
# server1/data/myid 1 # server2/data/myid 2 # server3/data/myid 3
統一 zoo.cfg(每節點相同):
tickTime=2000 initLimit=5 syncLimit=2 dataDir=./data dataLogDir=./dataLog clientPort=2181 # server1: 2181, server2: 2182, server3: 2183 maxClientCnxns=60 # 叢集配置:server.編號=IP:通訊埠:選舉埠 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890 # 單機模擬需修改各節點 clientPort 避免衝突 # server1: clientPort=2181 # server2: clientPort=2182 # server3: clientPort=2183
# 分別進入各節點 bin 目錄啟動 cd server1/apache-zookeeper-3.7.0-bin/bin ./zkServer.sh start cd server2/apache-zookeeper-3.7.0-bin/bin ./zkServer.sh start cd server3/apache-zookeeper-3.7.0-bin/bin ./zkServer.sh start # 檢查狀態 ./zkServer.sh status # 輸出應包含 Mode: leader / follower # 客戶端連線測試 ./zkCli.sh -server 127.0.0.1:2181 [zk: 127.0.0.1:2181(CONNECTED) 0] ls / [zk: 127.0.0.1:2181(CONNECTED) 1] create /app1 "data1" [zk: 127.0.0.1:2181(CONNECTED) 2] get /app1
/ <-- 根節點
├── app1 <-- 持久節點
│ ├── config <-- 子節點
│ │ ├── db.url
│ │ └── db.password
│ └── servers <-- 父節點
│ ├── server1 <-- 臨時順序節點 (EPHEMERAL_SEQUENTIAL)
│ ├── server2
│ └── server3
├── locks <-- 分散式鎖目錄
│ └── resource-lock
└── config <-- 全域配置
└── feature.flags
| 類型 | 建立方式 | 生命週期 | 可有子節點 | 典型用途 |
|---|---|---|---|---|
| 持久節點 | create /path data | 永久存在,除非刪除 | 是 | 配置資料、服務註冊 |
| 臨時節點 | create -e /path data | Session 結束自動刪除 | 否 | 服務心跳、鎖持有者 |
| 持久順序 | create -s /path data | 永久,自動附加序號 | 是 | 全域佇列、排序 |
| 臨時順序 | create -e -s /path data | Session 結束刪除,帶序號 | 否 | 分散式鎖、Leader 選舉 |
version、cversion(子節點)、aversion(ACL)-1 表示跳過版本檢查set /path data versionorg.apache.zookeeper zookeeper 3.7.0 org.slf4j slf4j-log4j12 org.apache.curator curator-framework 5.2.1 org.apache.curator curator-recipes 5.2.1
public class ZkBasicOps {
private static final String CONNECT_STRING = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
private static final int SESSION_TIMEOUT = 5000;
public static void main(String[] args) throws Exception {
// 1. 建立連線
ZooKeeper zk = new ZooKeeper(CONNECT_STRING, SESSION_TIMEOUT, event -> {
System.out.println("Watch 事件: " + event.getType() + " - " + event.getPath());
});
// 等待連線建立
while (zk.getState() != ZooKeeper.States.CONNECTED) {
Thread.sleep(100);
}
// 2. 建立持久節點
String path = zk.create("/app/config", "initial".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("建立節點: " + path);
// 3. 讀取資料(註冊 Watch)
byte[] data = zk.getData("/app/config", true, null);
System.out.println("資料: " + new String(data));
// 4. 更新資料
zk.setData("/app/config", "updated".getBytes(), -1);
// 5. 子節點列表
List children = zk.getChildren("/app", true);
System.out.println("子節點: " + children);
// 6. 刪除節點
zk.delete("/app/config", -1);
// 7. 關閉
zk.close();
}
}
@Configuration
public class CuratorConfig {
@Bean(initMethod = "start", destroyMethod = "close")
public CuratorFramework curatorFramework() {
return CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183")
.sessionTimeoutMs(5000)
.connectionTimeoutMs(3000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.namespace("myapp") // 隔離命名空間
.build();
}
}
@Service
public class ConfigService {
@Autowired CuratorFramework client;
public String getConfig(String key) throws Exception {
byte[] data = client.getData().forPath("/config/" + key);
return new String(data);
}
public void watchConfig(String key, Runnable onChange) throws Exception {
PathChildrenCache cache = new PathChildrenCache(client, "/config", true);
cache.getListenable().addListener((c, event) -> {
if (event.getType() == Type.CHILD_UPDATED) {
onChange.run();
}
});
cache.start();
}
}
// 應用啟動時讀取配置 + 註冊監聽
public void initConfig() {
// 1. 讀取全量配置
List keys = client.getChildren().forPath("/config");
for (String key : keys) {
String value = new String(client.getData().forPath("/config/" + key));
configMap.put(key, value);
}
// 2. 監聽變更
NodeCache cache = new NodeCache(client, "/config");
cache.getListenable().addListener(() -> {
refreshConfig();
});
cache.start();
}
// 服務啟動註冊(臨時節點)
public void registerService(String serviceName, String address) throws Exception {
String path = "/services/" + serviceName + "/" + address;
client.create().creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath(path, address.getBytes());
}
// 服務發現(監聽子節點變更)
public void discoverServices(String serviceName) throws Exception {
PathChildrenCache cache = new PathChildrenCache(client, "/services/" + serviceName, true);
cache.start();
cache.getListenable().addListener((c, event) -> {
List instances = cache.getCurrentData().stream()
.map(d -> new String(d.getData()))
.collect(Collectors.toList());
// 更新本地服務列表
loadBalancer.updateInstances(instances);
});
}
public void executeWithLock(String resourceId, Runnable task) throws Exception {
InterProcessMutex lock = new InterProcessMutex(client, "/locks/" + resourceId);
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
task.run();
} finally {
lock.release();
}
} else {
throw new RuntimeException("獲取鎖超時");
}
} catch (Exception e) {
throw new RuntimeException("鎖操作異常", e);
}
}
public void startLeaderElection(String electionName, LeaderSelectorListener listener) {
LeaderSelector selector = new LeaderSelector(client, "/election/" + electionName, listener);
selector.autoRequeue(); // 失去領導權後重新參選
selector.start();
}
// 實現 Listener
public class MyLeaderListener implements LeaderSelectorListener {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println("成為 Leader,執行主節點任務...");
// 執行定時任務、協調工作等
Thread.sleep(Long.MAX_VALUE); // 持有領導權直到異常
}
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
if (newState == ConnectionState.LOST) {
System.out.println("Session 丟失,喪失領導權");
}
}
}
| 指標 | 正常範圍 | 異常處理 |
|---|---|---|
| Pending Syncs | 0 | >10 表示 Follower 落後,檢查網路/磁碟 |
| Latency (min/avg/max) | ms 級 | max > 100ms 檢查 GC/磁碟 IO |
| Packets Received/Sent | 持續增長 | 停滯表示連線問題 |
| Outstanding Requests | < 10 | 積壓表示處理緩慢 |
| File Descriptor Usage | < 80% | 調大 ulimit -n |
ZooKeeper 以「簡單介面、強一致性、高可靠性」成為分散式系統協調基石。掌握其資料模型、Watch 機制、ZAB 協議特性,並善用 Curator 高階 API,能高效構建配置中心、服務發現、分散式鎖、Leader 選舉等核心基礎設施。