ZooKeeper:协调集群的接管、维护与恢复
ZooKeeper 保存服务注册、主节点身份、锁和配置版本等协调状态。维护一个节点时,影响会沿两条路径传到应用:集群可能重新选举 Leader;连接该节点的客户端需要重连。前者决定新写入何时能够提交,后者关系到 Session、临时节点以及应用能否继续承担原来的职责。
理解 znode、Watch、版本检查和 ACL 的首次操作,可先读 ZooKeeper 产品基础。接管集群时,还需要把这些对象和进程、磁盘、成员配置放到一起检查。
接管一个 ensemble,要认出哪些运行对象
成员身份与通信方向
一个 ensemble 是一组共同维护协调状态的 ZooKeeper Server。常见部署包含三个或五个参与投票的成员。三参与者需要两票,五参与者需要三票;Observer 接收状态和服务客户端,不增加投票数。决定可用性的还包括成员之间能否通信,而不仅是进程是否存在。部署要求和成员配置见 官方管理手册。
应用连接串:z1:2181,z2:2181,z3:2181/app
│ └─ chroot:应用看到的根路径
└─ 可尝试连接的成员地址;一个 Session 通常连接其中一台
成员 z2
├─ dataDir/myid = 2 对应 server.2,集群内唯一
├─ 2181 客户端读写和 Session 通信
├─ 2888 Leader 与 Follower 的同步通信
├─ 3888 选举通信
├─ dataDir 快照、成员身份等
└─ dataLogDir 事务日志;与普通运行日志分开先从正在运行的配置确认地址,不要根据目录名推断。服务器上的 myid 必须对应同一份成员列表里的 server 编号;复制目录到替换机器后,这个文件尤其容易遗漏。容器内部地址还要能被其他成员解析和访问,不能填成只在宿主机有效的回环地址。
Linux 发行包部署可从服务单元的 ExecStart、User、工作目录找到实际配置,再用 ss 核对监听。服务账号应能写自己的数据和日志目录,客户端账号则由 ZooKeeper 鉴权与 ACL 决定,两套权限分别配置。
写入、读取和 Session 的关系
客户端可以连接 Follower。写请求会进入 Leader 的提交流程,达到协议要求后再应用到状态树。Leader 故障期间,选举与同步会造成短暂停顿;少数派分区无法继续提交正常写入。某台机器还能返回旧数据时,仍需验证写入路径。
普通读取由所连接服务器处理,可能落后于另一个客户端刚完成的写入。需要跨客户端的读取顺序时,应结合业务使用版本检查、适当的 sync 与客户端协议;具体顺序保证见 Programmer's Guide。
Session 有协商后的超时时间。网络短断时,客户端可以带原 Session 重连到其他成员;服务端判定 Session 过期后,会删除属于它的临时节点。应用收到过期事件后,需要重新建立 Session,并重新注册服务或参与选举。曾经持锁的进程若因长时间 GC 停顿恢复,不能仅凭本地变量继续写外部资源;锁保护的资源还应校验 fencing token 等单调身份,拒绝旧持有者。
Watch 用于提示状态发生变化,处理通知后重新读取所需状态。一次性 Watch 要重新注册;持久 Watch 也要处理断连、状态重建和并发修改。每次回调不承担可靠业务消息的投递职责。相关用法和锁配方见 官方 Recipes。
用三节点实验观察多数派
启动前的环境与配置
使用 Linux、Bash、Docker Engine 和 Compose v2,镜像固定为 zookeeper:3.9.5-jre-17。三个容器放在一个宿主机上,只用于观察协议行为;宿主机失效时三个成员会一起消失。镜像环境变量和目录约定见 Docker Official Image 说明。
宿主操作者需要 Docker 权限。这通常等同于主机高权限。下面没有 Java 构建容器;ZooKeeper 镜像入口负责初始化卷并启动服务,辅助备份容器会明确以 root 读取卷。实际 Java 身份通过进程列表检查,不能拿 docker exec 启动的新进程身份替代。
在新的实验目录创建 compose.yaml:
name: ops4-zk
x-common: &common
image: zookeeper:3.9.5-jre-17
restart: "no"
environment: &env
ZOO_TICK_TIME: 2000
ZOO_INIT_LIMIT: 10
ZOO_SYNC_LIMIT: 5
ZOO_4LW_COMMANDS_WHITELIST: srvr,mntr,ruok
ZOO_AUTOPURGE_PURGEINTERVAL: 1
ZOO_AUTOPURGE_SNAPRETAINCOUNT: 5
ZOO_SERVERS: >-
server.1=z1:2888:3888;2181
server.2=z2:2888:3888;2181
server.3=z3:2888:3888;2181
services:
z1:
<<: *common
hostname: z1
environment:
<<: *env
ZOO_MY_ID: 1
volumes: [z1-data:/data, z1-log:/datalog]
z2:
<<: *common
hostname: z2
environment:
<<: *env
ZOO_MY_ID: 2
volumes: [z2-data:/data, z2-log:/datalog]
z3:
<<: *common
hostname: z3
environment:
<<: *env
ZOO_MY_ID: 3
volumes: [z3-data:/data, z3-log:/datalog]
volumes:
z1-data:
z1-log:
z2-data:
z2-log:
z3-data:
z3-log:没有发布宿主端口,CLI 在容器内执行。实验使用开放 ACL,不能将这个网络直接接入生产或公网。
docker compose config --quiet
docker compose up -d
docker compose ps
for member in z1 z2 z3; do
docker compose exec -T "$member" zkServer.sh status
done
docker compose top
docker compose exec -T z1 cat /conf/zoo.cfg /data/myid等待出现一台 leader、两台 follower 后再写入。初次启动的选举日志正常;持续无法形成这个组合时,先核对成员地址和端口,再看三台日志中的连接错误。ruok 只检查进程是否应答,成员角色和实际写入需要另外观察。
写入成功后,停掉一个成员
用非交互 CLI 创建一个持久节点。-waitforconnection 等待连接建立,外层 timeout 限制整个操作;脚本判断要检查输出内容,不能只信 CLI 的退出码。
printf 'create /ops4 v1\nget -s /ops4\nquit\n' |
docker compose exec -T z1 timeout 20 \
zkCli.sh -waitforconnection -timeout 5000 -server z1:2181应看到 Created /ops4、v1 和 Stat 字段。dataVersion 初始为 0;ephemeralOwner 为 0 表明它是持久节点。重复执行会得到 NodeExists,此时读取已存在节点,或另选一个新实验路径,不要顺手删除业务路径。
docker compose stop z3
printf 'set /ops4 v2\nget /ops4\nquit\n' |
docker compose exec -T z1 timeout 20 \
zkCli.sh -waitforconnection -timeout 5000 -server z1:2181,z2:2181若 z3 原来是 Leader,需要等 z1、z2 选举完成。随后读取应为 v2。这时冗余已经用尽,维护窗口内再损失一个参与者就无法继续正常写入。
再停一个成员,验证恢复后的状态
docker compose stop z2
printf 'set /ops4 v3\nquit\n' |
docker compose exec -T z1 timeout 15 \
zkCli.sh -waitforconnection -timeout 3000 -server z1:2181预期无法完成正常写入,可能看到连接错误或外层超时。此处是故意制造的负例,不应继续执行自动重启全部节点、删除事务日志等动作。
恢复 z2,让两台成员重新具备多数派,再读状态:
docker compose start z2
docker compose exec -T z1 zkServer.sh status
docker compose exec -T z2 zkServer.sh status
printf 'get /ops4\nquit\n' |
docker compose exec -T z1 timeout 20 \
zkCli.sh -waitforconnection -timeout 5000 -server z1:2181,z2:2181
docker compose start z3
docker compose exec -T z3 zkServer.sh status在这个已停止两台成员的实验中,读取应仍为 v2。一般故障中的 ConnectionLoss 则可能发生在服务器提交之后、客户端收到回复之前。恢复连接后,应通过业务标识和版本查明结果,再决定是否重试,尤其是创建顺序节点等非幂等操作。
维护磁盘、成员和协调状态
日志清理与单节点备份
dataDir 的快照与 dataLogDir 的事务日志共同构成恢复输入。快照文件名中的 zxid 是事务位置;运行日志则主要用于诊断。autopurge 按快照保留数量清理相应旧文件,仍需观察磁盘占用增长。完整清理语义见 管理手册的数据文件章节。
docker compose exec -T z3 sh -c 'df -h /data /datalog; du -sh /data /datalog'
docker compose logs --tail 100 z3若需要保全单节点文件,应先确认另两台正常,再停止该成员,复制同一停机点的两个目录。下面只创建本实验的备份,不覆盖任何目录:
mkdir -m 700 zk-backup
docker compose exec -T z1 zkServer.sh status
docker compose exec -T z2 zkServer.sh status
docker compose stop z3
Z3_ID=$(docker compose ps -a -q z3)
test -n "$Z3_ID"
docker run --rm --user 0 --volumes-from "$Z3_ID:ro" \
--entrypoint tar zookeeper:3.9.5-jre-17 \
-C / -czf - data datalog > zk-backup/z3-state.tar.gz
docker compose config > zk-backup/compose-resolved.yaml
sha256sum zk-backup/z3-state.tar.gz
tar -tzf zk-backup/z3-state.tar.gz
docker compose start z3只有容器归档命令和 tar -tzf 都成功时,才把文件纳入备份清单;重定向在命令失败时也可能留下空文件或截断文件。备份归宿主操作者所有,归档内保留原有文件身份。另存配置、JDK/镜像版本、集群成员和恢复说明。生产配置含凭据时,解析后的 Compose 文件也属于敏感备份。恢复前先验证校验值、归档条目和目标路径,避免把绝对路径或不可信归档直接解压到系统根目录。
这份停单节点的文件副本适合保全其停机时状态;业务继续写入后,它会落后于活跃集群。可用集群中的替换成员应从健康成员同步追平,而不是强迫全组回到备份位置。
单成员重建与全组恢复是两种操作
单成员磁盘损坏时,保留损坏卷做诊断,把该成员停下;确认另外成员仍能形成多数派,并核对需要重建的 myid。在新卷上按当前版本的成员初始化流程加入原 ensemble,让它从 Leader 同步,核对角色、同步状态与客户端读写后再处理其他机器。新节点身份、初始化标记和动态成员变更按 管理手册及 动态重配置指南执行。不要对运行中的多个卷同时清空数据。
全组都失去可信状态时,需要先停止依赖应用的协调写入,隔离旧集群和旧客户端,再选定一套相互匹配的恢复材料。ZooKeeper 3.9 的管理快照/恢复接口有独立的鉴权和操作前提,具体流程见 Snapshot and Restore Guide。恢复完成后还要核对持久路径、数据版本和 ACL,并让应用重新建立 Session、注册和选举。
临时节点表示某个 Session 当前持有的状态。恢复出旧目录里的节点内容,不足以让失效的锁持有者重新获得外部资源访问权。对 HBase、旧版 Kafka 或自研选主系统,还要遵守该系统的元数据恢复流程,防止协调状态与实际业务存储互相矛盾。
滚动升级的推进条件
确定目标版本支持的升级路径、客户端兼容性和配置变化后,在隔离环境恢复一份样本状态,测试实际依赖客户端。发行与升级入口见 ZooKeeper Releases。
生产通常逐个处理 Follower,等节点重新加入、状态追平且读写恢复后再处理下一台,最后处理 Leader。每一步都需要保留多数派;把三容器依次重启,并不能证明跨机器网络、磁盘和进程升级都安全。若当前节点迟迟无法同步,停在这一步,核对日志与版本,不要继续减少健康成员。
回退也取决于版本间的数据格式和协议兼容。升级后的目录不能默认交给旧二进制直接读取;保留原卷、配置和批准的恢复路径。
沿失败对象定位问题
| 现象 | 首先观察 | 处理方向与恢复确认 |
|---|---|---|
| 三台进程都在,却选不出 Leader | 各台 myid、server.*、2888/3888 双向连通与 DNS | 修正不一致配置或网络;等一主两从出现,再实际写读 |
| 一台启动失败 | 该台日志、目录权限、磁盘与事务文件错误 | 保全原文件;若多数派健康,仅替换故障成员并同步 |
| 偶发 Session 过期 | 协商超时、客户端 GC、网络中断、服务端 fsync 延迟 | 先消除停顿;观察重新注册与锁身份,不盲目放大超时 |
| 延迟增长、请求堆积 | outstanding requests、延迟、节点数、Watch/连接数、磁盘延迟 | 限制大节点与高频写入,定位监听风暴;比较负载回落后的延迟 |
| NoAuth | 实际认证身份、目标 znode ACL、父节点权限 | 按目标操作修正最小权限;创建/删除与读写检查的节点可能不同 |
| BadVersion | 当前 Stat 与提交的预期版本 | 重新读取并做并发决策,不能简单去掉版本条件 |
mntr、AdminServer 和指标端点只能开放给受控管理网络。四字命令白名单宜最小化,生产不要为了方便开放所有命令。TLS、SASL、ACL 和审计覆盖不同环节:加密链路、证明身份、授权路径操作、记录行为;具体配置见 管理手册的安全配置。
完成隔离实验后,在实验目录执行 docker compose down,容器和实验网络会停止移除,命名卷与备份仍保留。确认不再需要恢复材料之后,再按精确卷名单独清理,不把 down -v 当作日常停机命令。
权威资料与规范地址
部署和恢复操作应对照运行版本查阅,尤其是成员初始化、动态重配置和管理快照接口。
协调语义与部署
| 查阅内容 | 官方地址 |
|---|---|
| ZooKeeper 管理手册 | 部署、配置与维护 |
| 客户端语义 | Programmer's Guide |
| 协调配方 | Recipes |
| 容器入口与变量 | Docker Official Image |
| 发行入口 | Releases |
数据、成员与安全
| 查阅内容 | 官方地址 |
|---|---|
| 数据文件 | Data File Management |
| 成员变更 | Dynamic Reconfiguration |
| 管理快照与恢复 | Snapshot and Restore |
| 安全配置 | Authentication Options |
