共识系统工程:ZAB、ZooKeeper 与 etcd 实践
本文基于模型知识整理(生成时未联网核对),关键结论建议对照经典文献复核。
一句话定义
ZooKeeper(ZAB 协议)与 etcd(Raft)把共识包装成"高可用元数据服务":全局唯一的层级键值空间 + 监听机制,支撑选主、配置、服务发现与分布式协调——但正确使用它们需要知道共识不提供什么。
为什么重要
这两者是云原生时代被依赖最广的协调组件(K8s 依赖 etcd,无数中间件曾依赖 ZooKeeper)。大量生产事故源于"以为共识系统能包办一切":拿它当消息队列、当数据库、当跨服务事务协调器。理解其能力边界是架构基本功。
前置知识
核心概念
- ZAB(ZooKeeper Atomic Broadcast):与 Paxos/Raft 同代的崩溃容错原子广播协议;epoch(任期)+ zxid(64 位事务 id,高 32 位 epoch、低 32 位计数)实现全序;崩溃恢复模式做 leader 选举与日志对齐。
- ZooKeeper 数据模型:层级 znode(类似文件树),支持 watch(变更通知)、ephemeral 节点(会话断开自动删除)、顺序节点(全局单调序号)。
- etcd:Raft 日志 + MVCC 多版本存储 + watch + lease(租约,类似 ephemeral);linearizable/serializable 两种读模式。
- 典型用法:临时节点 + watch = 存活检测与选主;顺序节点 = 分布式队列/锁公平性;lease = 心跳注册。
原理与机制
共识系统能提供的:线性一致的(或可配置的)元数据存储、全序事件流(watch 有序推送)、高可用的少量数据(GB 级以内)。不提供的:大容量存储(写入吞吐低、日志全量复制)、消息队列语义(无堆积消费模型)、跨服务的分布式事务。拿 ZooKeeper 当 MQ 用(ZooKeeper 官方文档明确列为反模式)会因写吞吐与会话风暴拖垮整个协调层。
会话与活性语义:ZooKeeper 的一致性承诺是顺序一致 + 写线性一致;客户端读可能落在 follower(旧读),需要 sync() 或读 leader 拿线性一致。会话由心跳维持,GC 停顿超过 session timeout 会被判死并删除临时节点——这正是"用 ZK 做锁,进程假死后锁被他人拿走"的经典风险来源( fencing 的必要性见 kp-021)。
运维要点:奇数节点(多数派经济性,kp-009);避免与业务共置抢 IO(fsync 抖动直接放大写延迟);快照 + 日志的备份恢复演练;滚动重启时的 leader 让位策略。
图示
应用A ──create(ephemeral /lock, session)──► ZooKeeper(enemble: 3/5 节点)
应用B watch(/lock)
A 会话心跳超时(如长GC) → znode 删除 → B 收到通知接管
A 恢复后仍以为持锁 → 必须靠 fencing token(ZXID/版本号)被拒绝
实例或案例
- Kafka 的 ZK→KRaft 迁移:消除双重协调(Kafka controller + ZK),降低运维复杂度,是"共识服务被内嵌化"的代表性演进。
- Kubernetes:所有资源状态存于 etcd;apiserver 的乐观并发(resourceVersion)建立在 etcd MVCC 之上。
- Dubbo/老版 Hadoop 生态:以 ZK 为注册中心与选主中枢;watch 机制的服务发现形态。
常见误区
- 误区一:"ZooKeeper 写吞吐可以随加节点扩展"。共识写入固定为多数派 RTT + 全量日志复制,加节点反而更慢;扩容方向是分片(多集群)而非加节点。
- 误区二:"临时节点等于完美的心跳锁"。会话超时是活性判据不是安全判据,假死进程复活必须处理 fencing(kp-021)。
- 误区三:"读 ZK 一定是最新值"。默认可能读到 follower 旧数据;强一致读需 sync/linearizable 模式,代价是 RTT。
与其他知识点的关系
- kp-019:etcd 的内核机制。
- kp-021:在共识系统上构建锁/租约与 fencing。
- kp-028:watch/lease 是服务发现的基础设施。
- kp-032:etcd 在 K8s 架构中的位置。
自测题
- zxid 的结构如何保证全序与崩溃恢复安全?
答:高 32 位 epoch 标识任期、低 32 位事务计数;恢复时比较 (epoch, 计数) 对齐日志,新 leader 只能延续更大 zxid 的历史。
- 为什么不要拿 ZooKeeper 当消息队列?
答:写吞吐低(全量共识复制)、znode 尺寸限制、无消费位移/堆积模型,watch 在大量节点时产生连接与通知风暴。
- etcd 的 serializable read 与 linearizable read 差别?
答:serializable 读本地状态不协商(快但可能旧);linearizable 需与多数派确认任期(ReadIndex),保证读到已提交最新值。
延伸阅读
- Flavio Junqueira 等, "ZooKeeper: Wait-free coordination for Internet-scale systems"(USENIX ATC 2011)。
- ZooKeeper 官方文档 "ZooKeeper Recipes and Solutions"。
- etcd 官方文档:design / lease / mvcc。