ZooKeeper
本文范围
ZooKeeper 这篇不用先背一堆分布式理论。你先把它理解成老系统里的一本“协调登记簿”:谁在线、谁是主、服务地址在哪里、配置版本是多少、某个节点有没有拿到锁,这些小而关键的状态都可以登记在里面。
它不是数据库,也不是缓存。数据库里放订单、用户、交易流水;ZooKeeper 里放的是协调状态。这个区别很重要,因为很多线上问题就是把 ZooKeeper 当成普通存储用,节点越建越多,写入越来越频繁,最后 Leader 压力、事务日志、快照、磁盘空间一起出问题。
老系统里最常见的依赖关系大概是这样:
| 系统 | ZooKeeper 常见用途 | 出问题时的表现 |
|---|---|---|
| Kafka 老集群 | Broker、Controller、Topic 元数据等协调信息 | Controller 选举异常、Broker 注册异常、Topic 变更失败 |
| Dubbo | 注册中心,Provider 临时节点,Consumer 订阅服务 | 服务忽上忽下、Provider 短暂消失、消费者找不到地址 |
| HBase | HMaster、RegionServer、集群状态协调 | Master 切换、RegionServer 状态异常、业务读写抖动 |
| 自研系统 | 配置、选主、分布式锁、在线节点 | 临时节点消失、锁卡住、监听风暴 |
这里要注意一句话:ZooKeeper 集群靠“过半节点”工作。三节点挂一台还能跑,挂两台就不行;五节点挂两台还能跑,挂三台就不行。所以生产上一般是 3 台或 5 台,很少搞 2 台或 4 台,因为偶数节点并不会让多数派容错更划算。
本文只讲部署运维。ZAB、Paxos、Curator 编程、完整分布式锁代码都不展开。原理只讲到能解释现象:为什么 Leader 挂了会短暂不可写,为什么 myid 写错会选不出主,为什么事务日志不清理会打满磁盘。
截至 2026-07-09,Apache ZooKeeper 官方发布页显示 3.9.5 是 current release,3.8.6 是 latest stable release。新环境可以优先评估这两个分支;如果是老 Kafka、Dubbo、HBase 集群,先看组件兼容性,再决定 ZooKeeper 版本,不要只看“越新越好”。
前置条件
ZooKeeper 集群最怕“机器能启动,但多数派、目录和端口没有对齐”。部署前至少准备:
- 3 台或 5 台 Linux 服务器,生产不要用 2 台或 4 台凑数。
- 统一 JDK、ZooKeeper 版本、主机名解析和时间同步。
- 独立的
dataDir和dataLogDir,不要把事务日志长期写到系统盘。 - 明确
2181、2888、3888、AdminServer 和 Prometheus 指标端口的访问范围。 - 依赖系统清单:Kafka 老集群、Dubbo、HBase 或自研系统的连接串、恢复手册和变更窗口。
部署前先准备什么
先不要急着解压安装包。ZooKeeper 部署之前,一般要先把 4 件事定下来:版本、JDK、端口、目录。
版本上,新部署优先评估 3.8.6 或 3.9.5。老系统先看依赖组件的兼容矩阵,比如 Kafka 是否还是 ZooKeeper 模式,Dubbo 客户端版本是否太老,HBase 是否使用外部 ZooKeeper。
JDK 上,先在机器上确认:
java -version能看到 Java 版本再继续。生产环境里不要每台机器随手装一个不同 JDK,ZooKeeper 节点、Kafka 节点、HBase 节点最好有清晰的版本基线。否则排故时你会遇到一种很烦的情况:配置一样,包一样,但某台机器就是启动行为不一样。
端口一般这样规划:
| 端口 | 作用 | 怎么开放 |
|---|---|---|
2181 | 客户端访问 ZooKeeper | 只给应用服务器、运维跳板开放 |
2888 | ZooKeeper 节点之间同步数据 | 只给 ZooKeeper 节点互通 |
3888 | ZooKeeper 节点之间选主 | 只给 ZooKeeper 节点互通 |
8080 | AdminServer 默认端口 | 只给运维内网开放,或关闭 |
7000 | Prometheus 指标示例端口 | 只给监控系统开放 |
如果机器上启用了 firewalld,可以先放通内网需要的端口:
firewall-cmd --permanent --add-port=2181/tcp
firewall-cmd --permanent --add-port=2888/tcp
firewall-cmd --permanent --add-port=3888/tcp
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload执行完以后,用下面命令看端口有没有监听:
ss -lntp | grep -E '2181|2888|3888|8080'刚安装完还没启动时,可能看不到这些端口,这是正常的。启动之后还看不到,就要回头查进程、日志和配置。
目录建议提前建好,不要让程序随便写到当前目录:
/opt/zookeeper 程序目录,通常做成软链
/etc/zookeeper 配置目录
/data/zookeeper/data 快照目录,也放 myid
/data/zookeeper/log 事务日志目录
/var/log/zookeeper 运行日志目录这里要注意:dataDir 和 dataLogDir 最好分开。dataDir 主要放快照和 myid,dataLogDir 放事务日志。如果不配置 dataLogDir,事务日志默认也会落在 dataDir,后面清理和排障会更乱。
中国大陆、内网和离线环境怎么装
ZooKeeper 安装本身不难,难的是包从哪里来、怎么校验、怎么在内网复现。
一般步骤是:
- 从 Apache ZooKeeper 官方发布页进入下载。
- 下载
apache-zookeeper-版本-bin.tar.gz,注意是bin包。 - 同时下载
sha512和asc签名文件。 - 在外网机器上完成校验。
- 把安装包、校验文件、JDK 包、配置模板和 systemd 模板放进企业制品库或内网文件仓库。
下载完先做校验:
sha512sum -c apache-zookeeper-3.9.5-bin.tar.gz.sha512看到 OK 才继续。如果校验失败,不要继续安装,重新下载包。这个检查解决的是“包有没有被截断、污染、替换”的问题。
如果公司要求做 GPG 签名校验,再执行:
gpg --import KEYS
gpg --verify apache-zookeeper-3.9.5-bin.tar.gz.asc apache-zookeeper-3.9.5-bin.tar.gz离线环境里,一般需要准备这些东西:
apache-zookeeper-3.9.5-bin.tar.gz
apache-zookeeper-3.9.5-bin.tar.gz.sha512
apache-zookeeper-3.9.5-bin.tar.gz.asc
KEYS
jdk-*.tar.gz
zoo.cfg.template
zookeeper.service
zk-healthcheck.sh
rollback-notes.md这里不要把第三方镜像源、临时网盘、Docker Hub 直连当成唯一方案。国内网络和企业代理经常变化,真正可靠的是公司内部可追溯的制品库。今天能访问的地址,半年后未必还在。
Docker 镜像可以用来本地验证,但老系统生产部署更推荐二进制包加 systemd。原因很简单:Kafka、Dubbo、HBase 这类老系统很多本来就是二进制包部署,统一用户、目录、日志和启动方式,排障会顺很多。
单机先跑起来
先用单机把命令链路跑通。单机不是生产推荐架构,但它适合确认包、JDK、目录、配置、端口都没问题。
第一步,新建运行用户和目录:
useradd -r -s /sbin/nologin zookeeper
mkdir -p /opt
mkdir -p /etc/zookeeper
mkdir -p /data/zookeeper/data
mkdir -p /data/zookeeper/log
mkdir -p /var/log/zookeeper如果用户已经存在,useradd 会报错,可以先查一下:
id zookeeper第二步,解压安装包:
tar -xf apache-zookeeper-3.9.5-bin.tar.gz -C /opt
ln -sfn /opt/apache-zookeeper-3.9.5-bin /opt/zookeeper软链的好处是以后升级方便。比如新版本解压到另一个目录,验证没问题后切换 /opt/zookeeper 软链即可。
第三步,复制配置文件:
cp /opt/zookeeper/conf/zoo_sample.cfg /etc/zookeeper/zoo.cfg如果没有 zoo.cfg 就新建,有就修改。单机最小配置可以先写成这样:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data/zookeeper/data
dataLogDir=/data/zookeeper/log
clientPort=2181
maxClientCnxns=200
autopurge.snapRetainCount=10
autopurge.purgeInterval=24
4lw.commands.whitelist=ruok,stat,srvr,mntr,conf,cons,dirs
admin.enableServer=true
admin.serverPort=8080第四步,修正权限:
chown -R zookeeper:zookeeper /opt/apache-zookeeper-3.9.5-bin
chown -R zookeeper:zookeeper /etc/zookeeper
chown -R zookeeper:zookeeper /data/zookeeper
chown -R zookeeper:zookeeper /var/log/zookeeper这一步解决的是启动用户没有权限写数据目录、日志目录的问题。很多启动失败其实不是配置错,而是目录权限错。
第五步,启动:
sudo -u zookeeper /opt/zookeeper/bin/zkServer.sh start /etc/zookeeper/zoo.cfg执行后应该看到类似 STARTED 的结果。接着查状态:
sudo -u zookeeper /opt/zookeeper/bin/zkServer.sh status /etc/zookeeper/zoo.cfg单机模式正常应该看到:
Mode: standalone再用四字命令验证:
echo ruok | nc -w 2 127.0.0.1 2181正常返回:
imok如果没有返回,先查这几个点:
ps -ef | grep -i '[z]ookeeper'
ss -lntp | grep 2181
tail -n 200 /opt/zookeeper/logs/*.log一般启动失败就看三类问题:JDK 不对、配置不对、目录权限不对。不要一上来重装,先看日志。
连接 ZooKeeper 做一次最小操作
服务起来以后,用客户端连一下:
/opt/zookeeper/bin/zkCli.sh -server 127.0.0.1:2181进去后先创建一个测试节点:
create /ops-check ok执行成功应该看到:
Created /ops-check查看节点内容:
get /ops-check应该看到 ok。再看节点元信息:
stat /ops-check这里会看到 czxid、mzxid、version、dataLength 等信息。你不需要一开始全背下来,先记住:这些信息能帮助你判断节点什么时候创建、什么时候修改、版本变了没有。
修改节点:
set /ops-check ok-2
get /ops-check删除节点:
delete /ops-check退出:
quit这里要注意节点类型:
| 节点类型 | 通俗理解 | 常见用途 |
|---|---|---|
| 持久节点 | 客户端断开后还在 | 配置、目录、固定信息 |
| 持久顺序节点 | 持久存在,并自动加序号 | 队列、有序任务 |
| 临时节点 | 会话结束后自动删除 | 服务注册、在线状态 |
| 临时顺序节点 | 会话结束后删除,并自动加序号 | 选主、分布式锁 |
Dubbo Provider 注册时常见的就是临时节点。服务进程挂了、网络断开太久、Full GC 卡太久,ZooKeeper 会话过期后,临时节点就会消失,消费者就会感觉服务下线。
用 systemd 托管
手工 zkServer.sh start 可以用于验证,但生产最好交给 systemd。这样进程退出可以自动拉起,日志也更容易看。
新建 /etc/systemd/system/zookeeper.service:
[Unit]
Description=Apache ZooKeeper
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=zookeeper
Group=zookeeper
Environment=JAVA_HOME=/usr/lib/jvm/java-11
Environment=ZOO_LOG_DIR=/var/log/zookeeper
ExecStart=/opt/zookeeper/bin/zkServer.sh start-foreground /etc/zookeeper/zoo.cfg
ExecStop=/opt/zookeeper/bin/zkServer.sh stop /etc/zookeeper/zoo.cfg
Restart=on-failure
RestartSec=10
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target这里要按你的机器实际 JDK 路径修改 JAVA_HOME。不确定路径时可以查:
readlink -f "$(which java)"加载并启动:
systemctl daemon-reload
systemctl enable --now zookeeper看状态:
systemctl status zookeeper
journalctl -u zookeeper -n 200 --no-pager访问后应该能看到进程处于 running。失败时先不要反复 restart,先看 journalctl,尤其是 JAVA_HOME、权限、端口占用、配置文件路径。
三节点集群部署
单机跑通以后,再搭三节点。假设三台机器是:
zk1.example.com
zk2.example.com
zk3.example.com三台机器都安装同样的 ZooKeeper 包,目录也保持一致。然后三台机器的 zoo.cfg 写成同一份:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data/zookeeper/data
dataLogDir=/data/zookeeper/log
clientPort=2181
maxClientCnxns=200
autopurge.snapRetainCount=10
autopurge.purgeInterval=24
4lw.commands.whitelist=ruok,stat,srvr,mntr,conf,cons,dirs
admin.enableServer=true
admin.serverPort=8080
server.1=zk1.example.com:2888:3888
server.2=zk2.example.com:2888:3888
server.3=zk3.example.com:2888:3888server.1=zk1.example.com:2888:3888 这行的意思是:
server.myid=主机名或IP:数据同步端口:选举端口这里最容易错的是 myid。每台机器都要在 dataDir 下面写一个 myid 文件。
在 zk1 上执行:
echo 1 > /data/zookeeper/data/myid
chown zookeeper:zookeeper /data/zookeeper/data/myid在 zk2 上执行:
echo 2 > /data/zookeeper/data/myid
chown zookeeper:zookeeper /data/zookeeper/data/myid在 zk3 上执行:
echo 3 > /data/zookeeper/data/myid
chown zookeeper:zookeeper /data/zookeeper/data/myid这里要注意:myid 只能写数字,不能写 server.1,也不要带空格。它必须和 server.x 中的 x 对上。比如 zk2 的 myid 就是 2。
启动三台:
systemctl start zookeeper逐台看状态:
/opt/zookeeper/bin/zkServer.sh status /etc/zookeeper/zoo.cfg正常情况下,三台里面应该有一台是 Leader,两台是 Follower:
Mode: leader
Mode: follower
Mode: follower你也可以批量看:
for host in zk1.example.com zk2.example.com zk3.example.com; do
echo "== $host =="
echo srvr | nc -w 2 "$host" 2181 | grep Mode
done如果三台都看不到 Leader,或者一直是 LOOKING,先查端口互通:
for host in zk1.example.com zk2.example.com zk3.example.com; do
echo "== $host =="
nc -zv "$host" 2181
nc -zv "$host" 2888
nc -zv "$host" 3888
done2181 是客户端端口,2888 和 3888 是集群内部端口。很多人只放了 2181,忘了放 2888/3888,结果服务能启动但集群选不出来。
ZooKeeper 集群与 ZAB / 快照日志链路图
这张图适合放在集群设计评审和上线检查里。评审时先看 ensemble 是不是 3 或 5 个投票节点,myid 是否和 server.x 一一对应,2181/2888/3888 是否按客户端和集群内部边界开放;上线后用 stat、srvr、mntr、dirs 去验证角色、quorum、连接数、watch、快照和事务日志。
这张图里有两个生产约束要提前说清:第一,Observer 只能扩展读和跨机房观察,不能增加投票容错;第二,dataDir 和 dataLogDir 的增长不是小事,事务日志、快照、watch 风暴和客户端重连风暴都可能把 Leader 写入链路拖慢。故障现场不要只看进程是否存在,要同时看 zk_server_state、zk_synced_followers、zk_outstanding_requests、zk_watch_count 和 echo dirs。
做一次 Leader 宕机演练
集群搭完不要马上交差,先停一次 Leader 看看能不能自动选主。
先找 Leader:
for host in zk1.example.com zk2.example.com zk3.example.com; do
echo "== $host =="
echo srvr | nc -w 2 "$host" 2181 | grep Mode
done假设 zk2 是 Leader,在 zk2 上停服务:
systemctl stop zookeeper再到剩余两台看:
/opt/zookeeper/bin/zkServer.sh status /etc/zookeeper/zoo.cfg
echo mntr | nc -w 2 127.0.0.1 2181 | grep zk_server_state应该能看到新 Leader 出现。这里你会发现,Leader 切换期间写请求会有短暂失败或卡顿,这是正常现象。ZooKeeper 不是不出故障,而是通过过半机制保证不会出现两个 Leader 各写各的。
演练结束后,把原 Leader 拉回来:
systemctl start zookeeper
/opt/zookeeper/bin/zkServer.sh status /etc/zookeeper/zoo.cfg它回来以后一般会成为 Follower,不会因为自己以前是 Leader 就抢回主。
zoo.cfg 重点配置怎么理解
zoo.cfg 不要只复制模板。下面这些参数跟生产故障直接相关。
tickTime 是基础时间单位:
tickTime=2000很多超时都按它的倍数算。不要为了“快速发现故障”随便调小。调太小以后,网络抖动、GC 停顿、磁盘卡顿都可能被误判成节点故障。
initLimit 控制 Follower 初始同步等待时间:
initLimit=10Follower 启动后要跟 Leader 建连接并同步数据。数据量大、磁盘慢、网络慢时,这个值太小会导致 Follower 加不进集群。
syncLimit 控制 Leader 和 Follower 心跳同步最大延迟:
syncLimit=5如果日志里经常看到 Follower 掉线、重新连接,要结合网络、磁盘 IO、GC 一起看,不要只改这个参数。
dataDir 和 dataLogDir 要分开:
dataDir=/data/zookeeper/data
dataLogDir=/data/zookeeper/logdataDir 里会放快照和 myid,dataLogDir 放事务日志。线上磁盘满时,先用下面命令看哪个目录在涨:
du -sh /data/zookeeper/data /data/zookeeper/log
echo dirs | nc -w 2 127.0.0.1 2181maxClientCnxns 限制单个客户端 IP 到单个 ZooKeeper 节点的连接数:
maxClientCnxns=200如果应用连接数太多,不要第一反应就设成 0。设成无限制只是把问题推给 ZooKeeper。正确做法是先查谁连得多:
echo cons | nc -w 2 127.0.0.1 2181 | head -n 100
ss -antp | grep ':2181' | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | headautopurge 是清理快照和事务日志用的:
autopurge.snapRetainCount=10
autopurge.purgeInterval=24意思是保留最近 10 份快照相关数据,每 24 小时清理一次。生产上建议开启,不要等磁盘满了再手工救火。
四字命令需要白名单:
4lw.commands.whitelist=ruok,stat,srvr,mntr,conf,cons,dirs如果没配白名单,你执行 echo stat | nc ... 可能没有结果或被拒绝。不要无脑配 *,开放巡检需要的几个就够。
四字命令怎么用
四字命令是 ZooKeeper 排障时最顺手的工具。先装 nc,然后这样用:
echo ruok | nc -w 2 127.0.0.1 2181返回 imok 说明服务有响应。没有返回就先查进程和端口。
看当前节点角色:
echo srvr | nc -w 2 127.0.0.1 2181输出里重点看:
Mode: leader
Mode: follower看监控指标:
echo mntr | nc -w 2 127.0.0.1 2181排障时常看这些字段:
zk_server_state
zk_num_alive_connections
zk_outstanding_requests
zk_znode_count
zk_watch_count
zk_ephemerals_count
zk_synced_followers看实际配置:
echo conf | nc -w 2 127.0.0.1 2181这条命令解决的是“我以为配置改了,但进程实际没用上”的问题。改完配置一定要看实际生效值。
看连接来源:
echo cons | nc -w 2 127.0.0.1 2181 | head -n 50连接数高、Dubbo 服务抖、Kafka Broker 反复注册时,这条命令很有用。
看数据目录大小:
echo dirs | nc -w 2 127.0.0.1 2181这条命令可以快速判断快照目录、事务日志目录是不是已经很大。磁盘告警时先看它。
AdminServer 和 Prometheus 指标
新版本 ZooKeeper 默认有 AdminServer,可以用 HTTP 方式查状态:
curl -s 'http://127.0.0.1:8080/commands'
curl -s 'http://127.0.0.1:8080/commands/stat'
curl -s 'http://127.0.0.1:8080/commands/mntr'访问后应该看到 JSON 或命令输出。失败时看两个点:
grep -n 'admin' /etc/zookeeper/zoo.cfg
ss -lntp | grep 8080如果你用 Prometheus,可以打开 metrics:
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
metricsProvider.httpPort=7000
metricsProvider.exportJvmInfo=true重启后验证:
curl -s http://127.0.0.1:7000/metrics | head这里要注意,8080 和 7000 都是运维端口,不要开放到公网。只允许监控系统和运维跳板访问。
事务日志、快照和清理
ZooKeeper 每次写操作都会形成事务日志,运行一段时间后会生成快照。你可以简单理解成:事务日志记录每一步修改,快照记录某个时间点的完整数据树。
先看目录:
ls -lh /data/zookeeper/data
ls -lh /data/zookeeper/log
du -sh /data/zookeeper/data /data/zookeeper/log一般会看到类似文件:
snapshot.10000002f
log.100000030如果事务日志增长特别快,一般不是“文件自己变大”,而是写请求多。常见原因有:
- 服务频繁上下线,临时节点反复创建删除。
- 应用把配置当高频数据写。
- 分布式锁实现不合理,节点创建删除太密集。
- 客户端重连风暴导致注册信息反复变化。
先查指标:
echo mntr | nc -w 2 127.0.0.1 2181 | grep -E 'zk_packets_received|zk_packets_sent|zk_outstanding_requests|zk_znode_count|zk_watch_count'
echo cons | nc -w 2 127.0.0.1 2181 | head -n 100再看日志:
journalctl -u zookeeper -n 300 --no-pager开启自动清理:
autopurge.snapRetainCount=10
autopurge.purgeInterval=24如果已经出现磁盘告警,需要手工清理,先看空间:
date
du -sh /data/zookeeper/data /data/zookeeper/log
find /data/zookeeper/data -maxdepth 2 -type f | sort | tail -n 20
find /data/zookeeper/log -maxdepth 2 -type f | sort | tail -n 20再用官方清理脚本,保留最近 10 份:
/opt/zookeeper/bin/zkCleanup.sh /data/zookeeper/log /data/zookeeper/data -n 10清理后验证:
du -sh /data/zookeeper/data /data/zookeeper/log
/opt/zookeeper/bin/zkServer.sh status /etc/zookeeper/zoo.cfg
echo ruok | nc -w 2 127.0.0.1 2181这里要特别注意,不要直接这么干:
rm -rf /data/zookeeper/data/version-2/*
rm -rf /data/zookeeper/log/version-2/*这不是清理,这是赌命。你可能把最新快照或未合并事务日志删掉,轻则节点启动变慢,重则节点起不来。
Leader 和 Follower 的原理怎么服务排障
ZooKeeper 集群里一般有一个 Leader,多个 Follower。客户端读请求可以由 Follower 处理,写请求最终要走 Leader。
写入大概是这样:
客户端发起写请求
如果请求到了 Follower,Follower 转给 Leader
Leader 生成事务 Proposal 和 ZXID
Leader 广播给 Follower
过半节点 ACK
Leader 提交事务
各节点应用事务
客户端收到成功响应这段流程不用背,但要能解释几个现象。
第一,Leader 挂了以后会短暂不可写。因为集群要重新选主,选出来以后才能继续处理写请求。
第二,磁盘慢会影响写入。因为事务要写日志,Follower 也要 ACK。磁盘 IO 长期打满,ZooKeeper 延迟会升高。
第三,网络抖动会导致误判。Follower 和 Leader 心跳超时,就可能进入重新选主或重新同步。
所以遇到选主异常,不要只盯着 ZooKeeper 配置。要同时看网络、磁盘、CPU、GC。
echo mntr | nc -w 2 127.0.0.1 2181 | grep -E 'zk_server_state|zk_synced_followers|zk_pending_syncs|zk_outstanding_requests'
df -h
iostat -x 1 3
journalctl -u zookeeper -n 300 --no-pager如果机器没有 iostat,一般需要安装 sysstat。内网环境提前把这个包准备好,不要故障时才发现装不了。
Kafka、Dubbo、HBase 要注意什么
Kafka 老集群
Kafka 新版本已经走 KRaft,Kafka 4.0 起移除了 ZooKeeper 模式。但很多老集群还在使用 ZooKeeper,所以运维时要先判断当前 Kafka 属于哪种模式。
检查配置:
grep -R "zookeeper.connect" /opt/kafka/config /etc/kafka 2>/dev/null
grep -R "process.roles" /opt/kafka/config /etc/kafka 2>/dev/null如果看到 zookeeper.connect,一般就是 ZooKeeper 模式。如果看到 process.roles、controller.quorum.voters,一般是 KRaft 模式。
ZooKeeper 模式下要注意:
- 不要手工删除 Kafka 在 ZooKeeper 里的关键节点。
- ZooKeeper 抖动会影响 Controller、Broker 注册和元数据变更。
- Kafka 升级和 ZooKeeper 升级不要混成一次大操作。
- 迁移到 KRaft 要按 Kafka 官方升级路径来,不是简单改一个配置。
Dubbo
Dubbo 使用 ZooKeeper 做注册中心时,Provider 通常注册临时节点,Consumer 订阅目录。
连接串不要只写一个地址,建议写多个:
dubbo.registry.address=zookeeper://zk1.example.com:2181?backup=zk2.example.com:2181,zk3.example.com:2181如果 Dubbo 服务忽上忽下,先查三边:
echo cons | nc -w 2 zk1.example.com 2181 | head -n 100
echo mntr | nc -w 2 zk1.example.com 2181 | grep -E 'zk_num_alive_connections|zk_avg_latency|zk_max_latency'
grep -iE 'session expired|connection loss|zookeeper|keeper' app.log这里要注意:已有 RPC 调用不一定因为注册中心短暂不可用立刻全部中断,但新服务发现、地址变更、重连恢复会受影响。
HBase
HBase 对 ZooKeeper 稳定性比较敏感。生产环境一般建议用稳定的外部 ZooKeeper 集群,而不是让很多系统混用一个没容量规划的 ZooKeeper。
如果 HBase 出现 Master 切换、RegionServer 状态异常,同时 ZooKeeper 也有会话超时、延迟升高、磁盘告警,就不要只在 HBase 上排。先把 ZooKeeper 的状态、连接数、磁盘和日志查一遍。
echo srvr | nc -w 2 zk1.example.com 2181
echo mntr | nc -w 2 zk1.example.com 2181
echo cons | nc -w 2 zk1.example.com 2181 | head -n 100日常巡检怎么做
ZooKeeper 巡检不要靠手感。建议写成脚本,每天或每小时跑一次,告警系统采集关键结果。
示例脚本:
#!/usr/bin/env bash
set -euo pipefail
HOST="${1:-127.0.0.1}"
PORT="${2:-2181}"
CFG="${3:-/etc/zookeeper/zoo.cfg}"
echo "== process =="
ps -ef | grep -i '[z]ookeeper' || true
echo "== port =="
ss -lntp | grep ":${PORT}" || true
echo "== status =="
/opt/zookeeper/bin/zkServer.sh status "$CFG" || true
echo "== ruok =="
echo ruok | nc -w 2 "$HOST" "$PORT" || true
echo
echo "== srvr =="
echo srvr | nc -w 2 "$HOST" "$PORT" || true
echo
echo "== mntr =="
echo mntr | nc -w 2 "$HOST" "$PORT" | grep -E 'zk_version|zk_server_state|zk_num_alive_connections|zk_outstanding_requests|zk_znode_count|zk_watch_count|zk_ephemerals_count|zk_synced_followers' || true
echo
echo "== dirs =="
echo dirs | nc -w 2 "$HOST" "$PORT" || true
echo
echo "== disk =="
df -h /data/zookeeper || true
du -sh /data/zookeeper/data /data/zookeeper/log 2>/dev/null || true执行:
bash zk-healthcheck.sh 127.0.0.1 2181 /etc/zookeeper/zoo.cfg正常应该看到进程存在、端口监听、ruok 返回 imok、角色是 leader 或 follower、磁盘空间正常。
巡检重点记住这几个:
| 项目 | 正常情况 | 异常时先查 |
|---|---|---|
| 角色 | 1 个 Leader,其他 Follower | myid、网络、2888/3888、防火墙 |
| 连接数 | 平稳 | 应用连接池、单 IP 连接、重连风暴 |
| 请求堆积 | zk_outstanding_requests 接近 0 | 磁盘 IO、CPU、客户端写入 |
| 磁盘 | dataDir/dataLogDir 可控 | autopurge、事务日志、运行日志 |
| 延迟 | 平稳 | GC、磁盘、网络、请求量 |
下面这张图可以作为故障接管时的第一屏。ZooKeeper 的问题不要从“重启试试”开始,而要先判断多数派是否还在、Leader 是否稳定、数据目录是否安全,再决定是恢复网络、清理日志、重建 Follower,还是回到业务侧修连接和 watch。
这张图的关键是“先保护多数派,再动数据目录”。如果只是单个 Follower 的快照或事务日志坏了,先备份坏目录再让它从健康集群同步;如果 quorum 已经不足,就不要在剩余节点上做高风险清理。每一步修复后,都要用四字命令、AdminServer 或 Prometheus 指标复核,再让依赖系统做业务侧验证。
常见故障 1:节点启动失败
现象一般是:
systemctl status zookeeper 显示 failed
zkServer.sh status 提示 Error contacting service
2181 没有监听先不要重装,按顺序查:
systemctl status zookeeper --no-pager
journalctl -u zookeeper -n 300 --no-pager
ss -lntp | grep -E '2181|2888|3888|8080'
ls -ld /data/zookeeper/data /data/zookeeper/log /var/log/zookeeper
cat /data/zookeeper/data/myid 2>/dev/null
grep -nE 'dataDir|dataLogDir|clientPort|server\\.' /etc/zookeeper/zoo.cfg
java -version一般对应关系是:
| 现象 | 常见原因 | 怎么处理 |
|---|---|---|
| 端口占用 | 2181 或 8080 被占 | 停冲突进程或改端口 |
| Permission denied | 目录权限不对 | chown -R zookeeper:zookeeper |
| 找不到 Java | JAVA_HOME 不对 | 修 systemd 环境变量 |
| 单机正常集群不正常 | myid 或 server.x 错 | 对照每台机器修 |
修完以后验证:
systemctl restart zookeeper
/opt/zookeeper/bin/zkServer.sh status /etc/zookeeper/zoo.cfg
echo ruok | nc -w 2 127.0.0.1 2181常见故障 2:集群选不出 Leader
现象:
节点长期 LOOKING
客户端写入失败
日志反复 election、timeout、connection refused先查是不是多数派不足。三节点至少要有两台互通,五节点至少要有三台互通。
for host in zk1.example.com zk2.example.com zk3.example.com; do
echo "== $host =="
echo srvr | nc -w 2 "$host" 2181 | grep Mode || true
done再查端口:
for host in zk1.example.com zk2.example.com zk3.example.com; do
echo "== $host =="
nc -zv "$host" 2181
nc -zv "$host" 2888
nc -zv "$host" 3888
done再查 myid:
cat /data/zookeeper/data/myid
grep '^server\\.' /etc/zookeeper/zoo.cfg如果 zk2 的 myid 写成了 1,或者 server.2 指到了别的机器,就很容易选主异常。
最后查机器状态:
df -h
top
iostat -x 1 3
journalctl -u zookeeper -n 300 --no-pager这里要注意:选主异常不一定是 ZooKeeper 自己坏了。网络、安全组、防火墙、DNS、磁盘 IO、CPU、GC 都可能让节点之间误判。
常见故障 3:磁盘满
ZooKeeper 磁盘满通常有三类:事务日志和快照太多、运行日志太多、系统盘本来就小。
先看空间:
df -h
du -sh /data/zookeeper/data /data/zookeeper/log /var/log/zookeeper
find /data/zookeeper -type f -printf '%s %p\n' | sort -nr | head -n 20
echo dirs | nc -w 2 127.0.0.1 2181如果是运行日志大,看 journald 或 zookeeper.out:
journalctl -u zookeeper --disk-usage
ls -lh /var/log/zookeeper
ls -lh /opt/zookeeper/logs如果是事务日志和快照大,先确认集群健康,再清理:
/opt/zookeeper/bin/zkServer.sh status /etc/zookeeper/zoo.cfg
echo ruok | nc -w 2 127.0.0.1 2181
/opt/zookeeper/bin/zkCleanup.sh /data/zookeeper/log /data/zookeeper/data -n 10清完要做两件事:
du -sh /data/zookeeper/data /data/zookeeper/log
grep -nE 'autopurge.snapRetainCount|autopurge.purgeInterval' /etc/zookeeper/zoo.cfg如果 autopurge 没开,就补上。不然这次清完,下个月还会满。
常见故障 4:连接数过高
现象:
Too many connections
zk_num_alive_connections 持续升高
应用连接 ZooKeeper 慢先看总连接数:
echo mntr | nc -w 2 127.0.0.1 2181 | grep zk_num_alive_connections再看谁连得多:
echo cons | nc -w 2 127.0.0.1 2181 | head -n 100
ss -antp | grep ':2181' | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head一般原因有:
- 应用每次请求都新建 ZooKeeper 连接。
- 某个网关或 NAT 后面堆了太多实例,单 IP 连接过多。
- 客户端重连风暴。
- Watch 注册太多,服务端压力上来以后连接更不稳定。
处理顺序是:先定位来源 IP,再查对应应用,再改连接复用或连接池。不要上来就把 maxClientCnxns=0,那只是把限流阀门拆掉。
常见故障 5:会话超时和临时节点消失
Dubbo 服务忽上忽下、临时节点消失,很多时候不是 ZooKeeper “删错了”,而是客户端会话过期了。
先看 ZooKeeper 延迟和连接:
echo mntr | nc -w 2 127.0.0.1 2181 | grep -E 'zk_avg_latency|zk_max_latency|zk_num_alive_connections|zk_outstanding_requests'
echo cons | nc -w 2 127.0.0.1 2181 | head -n 100再看应用日志:
grep -iE 'session expired|connection loss|zookeeper|keeper' app.log如果怀疑应用 GC 卡住:
jstat -gcutil <pid> 1000 10常见原因:
| 原因 | 现场表现 |
|---|---|
| 应用 Full GC 太久 | 心跳发不出去,临时节点过期 |
| 网络抖动 | 客户端反复 connection loss |
| ZooKeeper 延迟高 | 多个应用同时会话抖动 |
| 会话超时配置太小 | 弱网络环境里频繁误下线 |
这里要注意,临时节点不是 TCP 一断就立即删除,而是会话过期才删除。如果网络短暂抖动但客户端及时重连,节点可能不会消失。
常见故障 6:数据目录损坏
如果某个 Follower 启动时报快照或事务日志损坏,先不要动其他节点。第一步是确认集群还有多数派健康:
for host in zk1.example.com zk2.example.com zk3.example.com; do
echo "== $host =="
echo srvr | nc -w 2 "$host" 2181 | grep Mode || true
done如果其他节点健康,只是单个 Follower 坏,可以先备份坏目录,再让它重新同步:
systemctl stop zookeeper
BACKUP_DIR=/data/backup/zookeeper-bad-$(date +%F-%H%M%S)
mkdir -p "$BACKUP_DIR"
mv /data/zookeeper/data "$BACKUP_DIR"/data
mv /data/zookeeper/log "$BACKUP_DIR"/log
mkdir -p /data/zookeeper/data /data/zookeeper/log
echo 2 > /data/zookeeper/data/myid
chown -R zookeeper:zookeeper /data/zookeeper
systemctl start zookeeper这里的 echo 2 要换成这台机器自己的 myid。不要复制示例就跑。
恢复后看状态:
/opt/zookeeper/bin/zkServer.sh status /etc/zookeeper/zoo.cfg
echo mntr | nc -w 2 127.0.0.1 2181 | grep -E 'zk_server_state|zk_znode_count|zk_synced_followers'如果是单机 ZooKeeper,不能这么处理。单机没有其他副本可以同步,只能从备份恢复。
备份和恢复
ZooKeeper 数据量通常不大,但一旦丢了,影响可能很大。备份不要只备一个 zoo.cfg。
一般要备这些:
/etc/zookeeper/zoo.cfg
/data/zookeeper/data
/data/zookeeper/log
/etc/systemd/system/zookeeper.service
JDK 和 ZooKeeper 版本记录
依赖系统的连接串记录备份示例:
BACKUP_DIR=/backup/zookeeper/$(hostname)-$(date +%F-%H%M%S)
mkdir -p "$BACKUP_DIR"
cp -a /etc/zookeeper "$BACKUP_DIR"/
cp -a /etc/systemd/system/zookeeper.service "$BACKUP_DIR"/
tar -czf "$BACKUP_DIR/data.tgz" -C /data/zookeeper data log
java -version > "$BACKUP_DIR/java-version.txt" 2>&1
/opt/zookeeper/bin/zkServer.sh version > "$BACKUP_DIR/zookeeper-version.txt" 2>&1 || true恢复时先判断场景:
| 场景 | 处理方式 |
|---|---|
| 单个 Follower 损坏 | 备份旧目录,保留正确 myid,从健康集群同步 |
| 单机 ZooKeeper 损坏 | 停机后用备份恢复 |
| 集群误删关键节点 | 先在隔离环境恢复确认,再规划回滚 |
| Kafka/Dubbo/HBase 节点误删 | 先查对应组件恢复手册,不要盲目手工补节点 |
恢复最怕一边恢复一边写。重大恢复操作前,先停相关业务写入或隔离故障集群,确认数据树和依赖组件状态后再放量。
落地工程深水区
ZooKeeper 的深水区在于“看起来只是一个协调服务”,实际却绑着 Kafka、HBase、Dubbo、注册发现、分布式锁和选主。运维要把它当成基础设施状态存储来管。
| 维度 | 生产约束 | 验证命令 |
|---|---|---|
| 集群规模 | 使用 3 或 5 个投票节点,明确故障容忍数量,避免偶数投票节点 | `echo mntr |
| 数据目录 | dataDir 和 dataLogDir 使用持久化磁盘,开启快照和事务日志清理 | ls -lh /data/zookeeper |
| 四字命令 | 只白名单开放巡检所需命令,不把所有 4lw 暴露给业务网段 | `echo conf |
| 会话治理 | session timeout、连接数、watch 数量和客户端重试策略一起评审 | `echo cons |
| 依赖治理 | 每个依赖系统记录根路径、owner、恢复手册,禁止故障时盲删 znode | zkCli.sh ls / |
上线后建议固定一组巡检口径:
for cmd in ruok stat mntr conf; do
echo "== $cmd =="
echo $cmd | nc zk-1 2181
done
zkCli.sh -server zk-1:2181 ls /如果 ZooKeeper 被多个系统共用,最重要的是隔离和责任边界:
- 根路径按系统隔离,Kafka、Dubbo、业务锁不要混在一个临时目录里。
- 业务禁止把 ZooKeeper 当大对象存储,不在 znode 里塞配置大文本或频繁变更的大 payload。
- 集群迁移要先迁客户端配置,再做双写或停写窗口,不能直接复制数据目录替换生产集群。
上线前后检查清单
上线前先确认:
[ ] ZooKeeper 版本、JDK 版本、业务组件兼容性已确认
[ ] 安装包、sha512、asc、KEYS 已归档
[ ] 三节点或五节点机器、主机名、IP、端口已确认
[ ] dataDir、dataLogDir 使用持久化磁盘
[ ] myid 与 server.x 一一对应
[ ] 2181、2888、3888 防火墙策略已限制范围
[ ] autopurge 已开启
[ ] systemd、日志轮转、监控、告警已配置
[ ] Kafka/Dubbo/HBase 等依赖系统连接串已准备上线后马上确认:
[ ] 每台节点 zkServer.sh status 正常
[ ] 只有一个 Leader
[ ] ruok 返回 imok
[ ] mntr 指标可采集
[ ] dataDir/dataLogDir 空间正常
[ ] 依赖系统能正常注册、发现或连接
[ ] 停 Leader 演练能自动选主
[ ] 重启 Follower 能重新加入集群故障时记住:
[ ] 先确认多数派是否可用
[ ] 先看角色、端口、日志、磁盘、连接数
[ ] 不直接删除 ZooKeeper 数据目录
[ ] 不手工删除 Kafka/Dubbo/HBase 的关键节点
[ ] 单节点恢复前先备份原 dataDir/dataLogDir
[ ] 修复后做业务侧验证,而不只看进程已启动常用配置模板
三节点生产基础模板可以从这里改:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data/zookeeper/data
dataLogDir=/data/zookeeper/log
clientPort=2181
maxClientCnxns=200
autopurge.snapRetainCount=10
autopurge.purgeInterval=24
4lw.commands.whitelist=ruok,stat,srvr,mntr,conf,cons,dirs
admin.enableServer=true
admin.serverPort=8080
metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
metricsProvider.httpPort=7000
metricsProvider.exportJvmInfo=true
server.1=zk1.example.com:2888:3888
server.2=zk2.example.com:2888:3888
server.3=zk3.example.com:2888:3888应用连接串不要只写一个地址:
zk1.example.com:2181,zk2.example.com:2181,zk3.example.com:2181只写一个地址的后果是:ZooKeeper 集群明明还活着,但应用因为唯一入口挂了而连不上。
