会用 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 场景。