ZooKeeper 进阶详解:ZAB 协议、Leader 选举、集群部署与会话机制
会用 ZK 的 API 只是第一步,真正决定系统行为的是集群内部机制:写请求为什么要绕到 Leader、读为什么可以直接本地应答、会话超时由谁判定。本文把这三块讲透,最后讨论一个常被回避的问题——ZooKeeper 到底不适合做什么。
集群角色:Leader、Follower、Observer
ZK 集群没有用传统的 Master/Slave 叫法,而是定义了三种角色:
| 角色 | 读请求 | 写请求 | 参与选举投票 | 参与写过半确认 |
|---|---|---|---|---|
| Leader | 可处理 | 唯一处理者 | 是 | 是 |
| Follower | 本地处理 | 转发给 Leader | 是 | 是 |
| Observer | 本地处理 | 转发给 Leader | 否 | 否 |
Leader 由选举产生,负责把所有写请求转成带 zxid 的事务并发起广播;Follower 是”完整公民”,既投票也参与写事务的过半确认;Observer 是 3.3 版本引入的”旁听生”,只同步数据、不参与任何投票。Observer 的价值在于扩读:读流量大了直接加 Observer,不会拖慢写路径(写延迟取决于过半 Follower 的确认速度),也不会改变选举的法定人数。跨机房读扩展是它的典型用法。
读写流程:写走 Leader、读走本地
客户端连上集群任意一台机器都能工作,但读写路径完全不同。
写流程:无论请求落到哪台机器,写最终都被转发给 Leader。Leader 生成一个全局递增的 zxid(高 32 位是 epoch,随每任 Leader 变化;低 32 位是该任内的递增计数),把请求封装成事务提案广播给所有 Follower。Follower 把提案写入本地事务日志后回 ACK;Leader 收齐过半 ACK 即提交该事务,再向各节点发 COMMIT。写吞吐因此受限于”Leader 与过半 Follower 的网络往返 + 磁盘 fsync”,节点数越多写越慢——这也是集群推荐 3 或 5 台而非更多的原因。
读流程:读请求由客户端当前连接的节点本地应答,不经过 Leader,也不走共识。换来的是读性能接近本地内存查询,但代价是可能读到略旧的数据。ZAB 保证的是顺序一致性:所有变更按 zxid 顺序在各节点应用,客户端看到的顺序不会错乱;只是”最新”这件事有延迟。需要读最新值时,客户端先调 sync 让本节点追平 Leader,再读。
选举期间(Leader 宕机到选出新 Leader)整个集群停止处理写请求,这是 CP 系统的固有代价,典型窗口在几十到几百毫秒,取决于 tickTime 与网络状况。
会话管理
会话是 ZK 一切”活性语义”的基础。客户端建立连接时协商 sessionTimeout,服务端实际生效值会被钳制在 minSessionTimeout 与 maxSessionTimeout(默认 2 倍和 20 倍 tickTime)之间。客户端周期性发心跳维持会话;服务端按过期时间分桶管理所有会话,到期批量清理,过期动作就是删除该会话的全部临时节点并触发事件。
两个容易踩的细节:
- 断连不等于过期。TCP 断开只是丢连接,客户端在超时窗口内重连上任意一台机器,会话继续有效,Watcher 需要客户端自行恢复注册(Curator 会代劳)。
- 会话可以迁移。重连到另一台机器时会话随之转移,服务端保证同一时刻只有一个连接持有该会话;如果旧连接”复活”再发请求,会收到 SESSION_MOVED 错误。
实践上,sessionTimeout 要大于预期网络抖动和 Full GC 停顿的 P99;JVM 堆过大导致的长 STW 是”会话误过期”最常见的元凶之一。
ZooKeeper 不适合做什么
注册中心的 CP 之争
ZK 做注册中心(Dubbo 经典用法)一直存在争议,核心在 CAP 取舍。注册中心的首要职责是”让消费者能调到提供者”,属于可用性敏感场景;而 ZK 是 CP 系统——Leader 宕机进入选举时,集群停止服务,期间新的提供者无法注册、消费者无法感知变更。跨机房部署时,一次网络分区可能让少数派机房的整个注册能力消失。
| 维度 | ZooKeeper | Eureka | Nacos |
|---|---|---|---|
| 一致性取向 | CP | AP | CP/AP 可切换 |
| 一致性协议 | ZAB | 无共识,对等复制 | Raft(持久实例)/ Distro(临时实例) |
| Leader 宕机时 | 选举期间停写 | 其余节点继续服务 | CP 模式下选主期间停写;AP 模式不停 |
| 数据健康检查 | 会话心跳 | 客户端续约 + 自我保护 | 客户端心跳/服务端探测 |
| 典型配套 | Dubbo、HBase | Spring Cloud Netflix | Spring Cloud Alibaba |
Eureka 走 AP 路线:节点间异步复制、允许短暂读到不一致的实例列表,用自我保护机制防止网络抖动时大面积摘除实例。Nacos 则允许按数据类型在 CP 与 AP 之间选择。如果业务只是要服务发现,Eureka 或 Nacos 的运维体验和故障行为通常比 ZK 更合适;如果场景本身要求强一致(如选主、分布式锁、元数据仲裁),ZK 的 CP 恰恰是优点。HDFS NameNode HA、HBase Master 选举至今仍建立在 ZK 上,原因正在于此。
其它不适合的场景
- 业务数据存储:单节点上限 1MB,全量数据驻留内存,写放大明显;把它当 KV 数据库会很快撞到容量和写性能天花板。
- 高频变更的载体:每次写都经过 Leader 广播 + 过半确认,每秒数千次写已接近合理上限,消息队列、实时位置上报这类场景不该用它。
- 海量节点监听:成千上万个 Watcher 意味着事件风暴和内存压力,Watcher 数量应与”实例数 × 关注点”同阶,而不是与数据条目同阶。
小结
- 写必经 Leader、读本地应答,是 ZK 读写不对称设计的核心,也是其性能与一致性特征的来源。
- 会话过期由服务端分桶判定,断连不等于过期;长 GC 是会话误杀的头号嫌疑。
- 注册中心选型是 CAP 取舍题:要可用性选 Eureka/Nacos,要强一致协调选 ZK,别让 ZK 去扛它本来就不擅长的 AP 场景。

