Apache ZooKeeper 是分布式協調服務的事實標準:命名、配置、同步、集群管理、Leader 選舉。本文提供**單機多實例模擬集群**的完整部署步驟、核心配置解析、Java API 核心操作演示,並針對生產環境關鍵參數給出調優建議。
# 1. 下載
wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz
tar -xzf zookeeper-3.4.8.tar.gz
# 2. 創建三個實例目錄
mkdir -p /opt/zookeeper/{server1,server2,server3}/{data,dataLog}
cp -r zookeeper-3.4.8 /opt/zookeeper/server1/
cp -r zookeeper-3.4.8 /opt/zookeeper/server2/
cp -r zookeeper-3.4.8 /opt/zookeeper/server3/
# server1/conf/zoo.cfg
tickTime=2000 # 基本時間單位 (ms)
initLimit=5 # Follower 初始化連接最長 5*tickTime (10s)
syncLimit=2 # Leader-Follower 同步最長 2*tickTime (4s)
dataDir=/opt/zookeeper/server1/data
dataLogDir=/opt/zookeeper/server1/dataLog
clientPort=2181 # 客戶端連接端口
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
# maxClientCnxns=60 # 單客戶端最大連接數,生產建議調大 (0 表不限制)
端口含義:`server.id=host:quorumPort:electionPort`。
myid 文件:各實例 `dataDir` 下創建 `myid`,內容為對應 id (1/2/3)。
# 分別啟動三個實例
/opt/zookeeper/server1/zookeeper-3.4.8/bin/zkServer.sh start
/opt/zookeeper/server2/zookeeper-3.4.8/bin/zkServer.sh start
/opt/zookeeper/server3/zookeeper-3.4.8/bin/zkServer.sh start
# 查看日誌確認 Leader 選舉完成
tail -f /opt/zookeeper/server1/zookeeper-3.4.8/zookeeper.out
首個啟動報錯屬正常 (連不到其他節點),全啟動後錯誤消失。
bin/zkCli.sh -server 127.0.0.1:2181
# 基本命令
ls / # 列出根節點
create /app1 "data" # 創建持久節點
get /app1 # 讀取數據
set /app1 "new data" # 更新數據
delete /app1 # 刪除節點
# Watch 測試
get /app1 watch # 註冊 Watch,另一終端 set /app1 觸發通知
Maven 依賴:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>
完整 Demo:
public class ZkDemo implements Watcher {
private static final String CONNECT_STRING = "192.168.19.130:2181,192.168.19.130:2182,192.168.19.130:2183";
private static final int SESSION_TIMEOUT = 30000;
private ZooKeeper zk;
public void connect() throws IOException {
zk = new ZooKeeper(CONNECT_STRING, SESSION_TIMEOUT, this);
}
@Override
public void process(WatchedEvent event) {
System.out.println("WatchedEvent >> " + event);
// 一次性觸發:業務邏輯處理後需重新註冊 Watch
}
public void demo() throws KeeperException, InterruptedException {
// 創建持久節點
zk.create("/zk-test", "init".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 讀取並註冊 Watch
byte[] data = zk.getData("/zk-test", true, null); // true = 註冊 Watch
System.out.println("Data: " + new String(data));
// 更新觸發 Watch
zk.setData("/zk-test", "updated".getBytes(), -1);
// 再次更新不觸發 (一次性特性)
zk.setData("/zk-test", "updated2".getBytes(), -1);
// 讀取不註冊 Watch
byte[] data2 = zk.getData("/zk-test", false, null);
// 刪除
zk.delete("/zk-test", -1);
// 判斷存在
Stat stat = zk.exists("/zk-test", false);
System.out.println("Exists: " + (stat != null));
}
public void close() throws InterruptedException {
zk.close();
}
public static void main(String[] args) throws Exception {
ZkDemo demo = new ZkDemo();
demo.connect();
demo.demo();
demo.close();
}
}
| 維度 | 建議 | 說明 |
|---|---|---|
| 部署架構 | 3/5 節點奇數部署,跨機架/可用區 | 容忍 (N-1)/2 節點故障,避免單點 |
| 存儲 | dataDir + dataLogDir 分盤 (SSD) | 事務日志順序寫分離,降低延遲抖動 |
| JVM | -Xms2g -Xmx2g -XX:+UseG1GC | 堆內存固定,避免 FullGC 停頓影響心跳 |
| 網絡 | 專用網段、關閉 swap、調大文件描述符 | 防止 GC/交換導致「假死」被踢出集群 |
| 客戶端 | Curator 框架 (重試、連接狀態監聽、Recipe) | 原生 API 過於底層,Curator 封裝分布式鎖/選舉/緩存 |
| 監控 | JMX 指標:請求延遲、隊列長度、Follower 同步狀態 | Prometheus + Grafana 告警:Leader 變更、會話過期率 |