主题
ZooKeeper、etcd、Consul 怎么选
一句话:三者都能存少量键值并通知变化,真正的选型依据是语言生态、Watch 语义和运维方式,而不是一张跑分表。
适用版本:ZooKeeper 3.8/3.9、etcd 3.5.x、Consul 1.20.x;具体补丁版本以官方文档为准。
为什么需要
分布式协调服务要同时提供三件事:一致的小量存储、变更通知、租约/会话。ZK 用 ZAB、etcd 和 Consul 用 Raft,理论上都能做选主、配置、服务目录。差别在于:你的技术栈已经绑定了谁、客户端库是否成熟、Watch 是「通知一次」还是「持续订阅」、跨数据中心和健康检查是不是刚需。
核心机制
| 维度 | ZooKeeper | etcd | Consul |
|---|---|---|---|
| 语言/生态 | Java,Curator 成熟 | Go,K8s 原生 | Go,服务发现/多 DC |
| 一致性协议 | ZAB | Raft | Raft |
| Watch | 一次性,需重注册 | 长连接 gRPC stream,可按 revision 续订 | 阻塞 HTTP 查询,带 index |
| 健康检查 | 无原生,靠临时节点 | 无原生,靠租约 | 原生 TCP/HTTP/gRPC/Script |
| 多数据中心 | 不支持原生多 DC | 不支持原生多 DC | WAN Join |
| 典型用途 | Dubbo、ElasticJob、HBase、Kafka ZK 模式 | K8s 元数据、选主、配置 | 服务目录、跨 DC 发现 |
| 运维 | JVM + 磁盘 + 会话 | 单二进制 + 磁盘 | 单二进制 + ACL |
协议差异:ZAB 与 Raft
两者都是 Leader 主导:客户端写请求交给 Leader,Leader 复制到多数派,提交后应用。Raft 的 AppendEntries 是 Leader 主动推给 Follower 的,不是 Follower 主动拉取;ZAB 的 Proposal 同样是 Leader 推送。
选举限制也经常被讲错。Raft 要求候选人的日志不落后于任何给它投票的节点,拿到多数票后,新 Leader 一定包含所有已提交日志;所谓「Raft 可能出现已提交日志不在新 Leader」是不对的。ZAB 以 zxid 最大者优先,目的相同。两者的差异在工程细节:ZAB 用 epoch + zxid 维护全序,Raft 用 term + log index,并规定 Leader 不直接提交旧任期日志,要等当前任期有一条日志提交后间接提交。安全性上没有谁天然更强。
Watch 语义:三种模型
| ZK | etcd | Consul | |
|---|---|---|---|
| 连接形态 | 短通知,触发即删除 | 长连接 stream | 长轮询 HTTP |
| 是否重放 | 不重放,必须全量重读 | 可从指定 revision 续订 | 不重放,用 index 保证一致读 |
| 客户端负担 | 重注册 Watch、维护本地状态 | 处理 revision、compaction | 处理阻塞超时和 index 回退 |
| 适用 | 低频变更、状态可全量重建 | 事件流、配置分发 | 服务目录、健康状态 |
ZK 的 Watch 只告诉你「变了」,不包含新值;通知和重新注册之间如果有多次变化,你只会知道「至少变过」,所以正确做法是收到通知后全量读取当前状态。etcd 的 Watch 是持续流,事件带 kv,且可以指定 --rev 从某个版本续订;但旧 revision 可能已被压缩,压缩后只能做全量重建。Consul 的阻塞查询拿 X-Consul-Index,下次带同一个 index 请求,服务端有变化才返回;它不重放事件,但 index 让客户端能判断自己读到的是否是最新状态。
生态与版本
- Kafka:ZK 模式存在于 3.9 及之前;KRaft 在 3.3 达到生产可用,4.0 只支持 KRaft、移除 ZooKeeper。讨论「Kafka 去 ZK」时要按这个时间线说,不能还当成未来计划。
- K8s:etcd 是 API Server 的状态存储,用了 K8s 就已经有 etcd,再引入 ZK 只会增加一套运维。
- Java 存量:Dubbo、ElasticJob、HBase 等深度绑定 ZK/Curator;换协调服务往往不是改配置,而是改代码里的选主、锁和监听逻辑。
- 多 DC + 健康检查:Consul 的 WAN Join 和原生健康检查是它相对另外两者的明确差异。
- Spring Cloud:Nacos 把服务发现和配置中心做在一起,通常比自建 ZK/etcd 更省事。
一句话选型:Java 存量选 ZK,K8s/Go 选 etcd,多 DC 与健康检查选 Consul,Spring Cloud 选 Nacos。高并发短锁可以选 Redis,但 Redis 不是协调服务,不承担成员管理和选主。
最小上手
同一组 KV 操作,三套单机环境放在一个 docker-compose.yml:
yaml
services:
zk:
image: zookeeper:3.9
ports: ["2181:2181"]
etcd:
image: quay.io/coreos/etcd:v3.5.15
command:
- --advertise-client-urls=http://0.0.0.0:2379
- --listen-client-urls=http://0.0.0.0:2379
ports: ["2379:2379"]
consul:
image: hashicorp/consul:1.20
command: ["agent", "-dev", "-client=0.0.0.0"]
ports: ["8500:8500"]bash
docker compose up -d
docker compose exec zk zkCli.sh -server 127.0.0.1:2181在 zkCli 内执行(get -w 只通知一次,另开终端 set /demo world):
text
create /demo hello
get /demo
get -w /demobash
# etcd 3.5
docker compose exec etcd etcdctl --endpoints=127.0.0.1:2379 put /demo hello
docker compose exec etcd etcdctl --endpoints=127.0.0.1:2379 get /demo
docker compose exec etcd etcdctl --endpoints=127.0.0.1:2379 watch /demo
# Consul 1.20
docker compose exec consul consul kv put demo hello
docker compose exec consul consul kv get demo
docker compose exec consul consul watch -type=key -key=demo观察差异:ZK 的 get -w 收到一次通知后必须重新注册;etcd 的 watch 会持续输出事件,Ctrl-C 才结束;Consul 的 watch 是阻塞查询,返回后要重新发起下一次。三者的「读」都应在收到变更后做全量重读,而不是假设通知里带着完整新状态。
生产动作
Watch 必须能全量重建。 不要用「事件 = 增量」的思路写业务;ZK 会丢中间事件,etcd 可能遇到 compaction,Consul 的阻塞查询也会超时重连。客户端启动时先全量拉一次,再开始监听,监听回调里再全量或按 revision 拉取。
选型前先看运维能力。 ZK 要管 JVM、会话、磁盘和 4lw;etcd 要管 Raft 成员、快照和 compaction;Consul 要管 ACL、WAN gossip 和健康检查。团队没有相应经验,再「先进」的协议也会变成事故源。
版本要对齐。 Kafka 3.9 是最后一个支持 ZK 的版本,4.0 只支持 KRaft;升级要经过 bridge 模式,不能直接把 ZK 删掉。ZK 3.5.3+ 的 4lw 默认只开 srvr,监控脚本要同步改白名单。etcd 3.5 默认 v3 API,老脚本里的 ETCDCTL_API=2 要清理。
容量按「小元数据」估算。 ZK 单节点 1MB、全量内存;etcd 默认单请求上限约 1.5MiB;Consul KV 也不是为大对象和高写入设计的。三者都不适合存业务大字段或做高频计数器。
边界
- 不做性能排行榜。 三者都能做到毫秒级读、几十到几百毫秒级写;具体数字取决于磁盘 fsync、网络和批次,必须用自己的负载压测。
- etcd 不是通用数据库。 它的强项是 K8s 元数据和小配置;大 value、高写入会放大 Raft 日志和快照成本。
- Consul 的 KV 不是主业务存储。 它的价值在服务目录、健康检查和多 DC;把业务配置全塞进去会掩盖 ACL 和容量问题。
- ZK 不适合大规模服务发现。 大量 Watch 同一节点会产生羊群效应;这也是 Nacos/Consul 在服务发现场景更常见的原因。
- 不要为了「统一技术栈」硬换。 Java 存量换 etcd,改的是 Curator 的选主、锁、监听代码;收益通常小于迁移成本。
讲清楚
选协调服务先看生态,不看跑分。Java 存量、Dubbo/ElasticJob/HBase 这些已经绑定 ZK 的系统,继续用 ZK + Curator 最省事;K8s 和 Go 技术栈天然有 etcd;需要跨数据中心和原生健康检查,选 Consul;Spring Cloud 体系直接用 Nacos。
协议上,ZAB 和 Raft 都是 Leader 主导、多数派提交,Raft 的日志是 Leader 推给 Follower 的,选举限制也保证新 Leader 拥有全部已提交日志。真正的工程差异在 Watch:ZK 一次性、要重注册、不重放;etcd 是可持续的流、能按 revision 续订;Consul 是带 index 的阻塞查询。选型时把「客户端要写多少状态机」算进去,比比较协议名字更有意义。