跳到正文

ZooKeeper、etcd、Consul 怎么选 ​

一句话:三者都能存少量键值并通知变化,真正的选型依据是语言生态、Watch 语义和运维方式,而不是一张跑分表。

适用版本:ZooKeeper 3.8/3.9、etcd 3.5.x、Consul 1.20.x;具体补丁版本以官方文档为准。

为什么需要 ​

分布式协调服务要同时提供三件事:一致的小量存储、变更通知、租约/会话。ZK 用 ZAB、etcd 和 Consul 用 Raft,理论上都能做选主、配置、服务目录。差别在于:你的技术栈已经绑定了谁、客户端库是否成熟、Watch 是「通知一次」还是「持续订阅」、跨数据中心和健康检查是不是刚需。

核心机制 ​

维度ZooKeeperetcdConsul
语言/生态Java,Curator 成熟Go,K8s 原生Go,服务发现/多 DC
一致性协议ZABRaftRaft
Watch一次性,需重注册长连接 gRPC stream,可按 revision 续订阻塞 HTTP 查询,带 index
健康检查无原生,靠临时节点无原生,靠租约原生 TCP/HTTP/gRPC/Script
多数据中心不支持原生多 DC不支持原生多 DCWAN 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 语义:三种模型 ​

ZKetcdConsul
连接形态短通知,触发即删除长连接 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 /demo
bash
# 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 的阻塞查询。选型时把「客户端要写多少状态机」算进去,比比较协议名字更有意义。

把一门技术讲透到既懂也会用