第 33 章 / 共 57 章 · 约 4 分钟
关键技术:ZooKeeper 与 etcd
ZooKeeper 和 etcd 是给“协调”用的强一致存储:数据量很小,但每次写入都经过共识协议,所有节点对顺序达成一致。选主、分布式锁、服务发现、集群配置这类“全集群必须对同一件事看法一致”的问题,最后往往落到它们身上。它们不是用来存业务数据的。
1. 核心模型
- 共识集群:通常 3 或 5 个节点,ZooKeeper 用 ZAB 协议,etcd 用 Raft。写入由 leader 发起,多数派(quorum)确认后才算提交。5 个节点能容忍 2 个故障,3 个节点能容忍 1 个;节点数取奇数,因为 4 个节点和 3 个节点一样只能容忍 1 个故障。
- 数据模型:ZooKeeper 是树形的 znode;etcd 是扁平的键值,按前缀查询。
- 会话和租约:客户端和集群保持心跳。ZooKeeper 的临时节点(ephemeral znode)、etcd 绑定租约(lease)的 key,在客户端失联、会话或租约过期后自动删除,这是“检测某个进程是否还活着”的基础。
- 顺序号:ZooKeeper 的顺序节点、etcd 每次修改的全局 revision 单调递增,可以用来排队和做 fencing token。
- 监听(watch):客户端订阅某个 key 或前缀,变化时收到通知,用于配置下发和服务列表更新。
- 读一致性:etcd 默认线性一致读(经过 leader 确认);ZooKeeper 的读由客户端连接的那台服务器直接返回,可能略旧,需要最新值时先调用
sync。
2. 什么时候用
- 选主:一组调度器里只能有一个在工作。大家去抢同一个带租约的 key,抢到的是 leader;leader 挂掉,租约过期,其他节点接管。
- 分布式锁:需要真正互斥、并且能配合 fencing token 的场景。
- 服务发现和配置:实例启动时注册临时节点,下线自动消失;配置变更通过 watch 实时推送。
- 集群元数据:Kubernetes 把整个集群状态存在 etcd;HBase 等系统用 ZooKeeper 做协调;早期 Kafka 也依赖 ZooKeeper,新版本改用内置的 KRaft。
- 小规模的唯一编号分配:一次分配一段号段,例如短链接服务里每台服务器从 ZooKeeper 租一段 ID。
3. 什么时候不适合
- 业务数据和大对象:容量小,值也小,写入吞吐有限。
- 高频写入:每次写都要过共识,每秒几千到几万次写就是上限量级,计数器、会话这类数据交给 Redis 或数据库。
- 跨地域低延迟写入:共识要多数派确认,节点跨洋分布时每次写都要跨洋往返。
- 只需要“尽量互斥”的场景:例如避免重复发同一封提醒邮件,Redis 锁或数据库唯一约束更简单。
4. 关键限制和数量级
| 维度 | 要记住的事 |
|---|---|
| 集群大小 | 3 或 5 个节点;节点越多写入越慢,因为要等更多确认 |
| 数据量 | etcd 默认存储配额 2 GB,建议不超过 8 GB;单个请求约 1.5 MB;ZooKeeper 单个 znode 默认上限约 1 MB |
| 写入吞吐 | 每秒几千到几万次,受磁盘 fsync 和网络往返限制 |
| 可用性 | 失去多数派就不能写入:这是 CAP 里选择一致性的代价 |
| 故障检测 | 会话 / 租约超时通常是几秒到几十秒,决定了故障切换的速度 |
5. 面试中常被追问的点
- 为什么节点数是奇数? 多数派是 ⌊n/2⌋+1。4 个节点要 3 个确认,只能容忍 1 个故障,和 3 个节点一样,却多一台机器、写入更慢。
- 旧 leader 以为自己还是 leader 怎么办(脑裂)? 旧 leader 可能因为 GC 暂停或网络分区,没察觉自己的租约已经过期。解决办法是 fencing token:每次选主得到一个更大的编号(etcd revision、ZooKeeper zxid),下游存储只接受编号不小于已见过最大值的写入。
- 用 ZooKeeper 实现锁的经典做法? 每个客户端在锁目录下创建临时顺序节点,编号最小的持有锁;其他客户端只 watch 比自己小一号的节点,避免锁释放时所有人一起被唤醒(羊群效应)。
- 集群一半节点挂了会怎样? 剩下的不够多数派,集群拒绝写入,依赖它的选主、锁都停摆。所以要把节点放在不同机架或可用区。
- 为什么不用 Redis 做选主? Redis 主从是异步复制,故障切换可能让两个客户端同时持锁;对正确性要求高的选主,要用共识系统或者加 fencing。
6. 和其他技术的取舍
| 选项 | 适合 | 代价 |
|---|---|---|
| etcd | Kubernetes 生态、gRPC 接口、线性一致读 | 数据量小 |
| ZooKeeper | Hadoop / HBase 等老牌生态、成熟的锁和队列配方 | 运维 JVM 集群;默认读可能略旧 |
| Consul | 服务发现加健康检查、多数据中心 | 另一个要运维的系统 |
| Redis 锁 | 简单、快,容忍偶发的重复执行 | 故障切换时不保证互斥 |
| 数据库行锁 / advisory lock | 已有数据库、低频协调 | 依赖数据库可用性,持锁时间不宜长 |
7. 本站案例
- 案例 1:短链接服务:每台服务器从 ZooKeeper 或数据库租一段 ID,避免冲突。
- 案例 9:分布式键值存储:无主设计用 gossip 传播成员信息;与之对比,每个分片有 leader 的系统通常把成员和分片映射放在 etcd / ZooKeeper 里。