48.6 产品 (etcd / Consul / ZooKeeper / Nacos)

一句话

四个产品都能做「KV + watch + 租约」,差别在一致性语义、数据模型、多数据中心与生态;选型不是选功能最多的,而是选「故障模型与运维能力匹配」的那个。

一张总表

维度 etcd Consul ZooKeeper Nacos
共识 Raft Raft ZAB 临时实例 Distro(AP);持久实例 Raft(CP)
默认读一致性 线性一致(可切 serializable 本地读) KV 读默认可能陈旧,consistent 才走 leader 读可能陈旧(follower 本地返回) 看实例类型/接口
数据模型 扁平 KV + revision KV + catalog + health checks + intentions znode 树 namespace / group / service / config
Watch 流式,按 revision 回放 blocking query(index + wait) 一次性 watch;3.6+ 持久 watch 长轮询 + MD5
租约/会话 lease + keepalive session + TTL check session + ephemeral 心跳/临时实例
服务发现 需上层封装 原生 catalog / DNS Curator 封装 原生 naming
配置 KV KV Curator 原生 config
多数据中心 无原生(mirror/learner 等方案) 原生多 DC(WAN gossip) observer / 手工 集群 + AP
运维形态 Go 单二进制,K8s 标配 Go agent + gossip JVM + 磁盘 + 会话 Java + 控制台
主要生态 Kubernetes HashiCorp / Service Mesh Hadoop / 旧 Kafka Spring Cloud Alibaba

⚠️ 这张表只能当「起点」:同一产品的不同 API、不同配置提供不同语义。比如 etcd 的 serializable 读、Consul 的 ?consistent、ZooKeeper 的 sync(),都会改变你拿到的一致性。

一致性:四个产品各自承诺什么

共识底座的细节见 21.5 Raft:选主、21.7 Raft:安全性证明;选型层面的算法对比见 21.12 共识算法对比。

产品 写 读 关键细节
etcd 线性一致(Raft 多数派提交) 默认线性一致;serializable 读本地、可能陈旧 watch 从 revision 回放;compaction 后旧 revision 不可读
Consul 线性一致(Raft) 默认可能陈旧;?consistent 走 leader blocking query 的 index 也可能落后;session 用于锁
ZooKeeper 线性一致(ZAB) 可能陈旧(读本地副本) 写后 sync() 再读才是新鲜;watch 一次性
Nacos 临时实例 AP、持久实例 CP 取决于接口与实例类型 临时实例靠心跳,分区时可用;持久实例走 Raft

🔑 最常见的一句话错误是「ZooKeeper 是强一致」。准确说法是:写线性一致 + 客户端 FIFO 顺序,读可能陈旧;需要新鲜读时先 sync()。

Watch 语义对比

产品 机制 断线续传 慢消费者
etcd 流式 watch,带 start_revision 从 revision 回放(compaction 窗口内) 慢 watcher 可能被断开或积压,客户端需重 LIST
Consul blocking query(index + wait) 带上次 index 继续 每个 watch 占一个连接;无服务端推送
ZooKeeper 标准 watch 一次性;3.6+ 持久 watch 重新注册 + 重新 get watch 数量受服务端内存限制
Nacos 长轮询(默认 30s 量级),MD5 比对 重新发起长轮询 服务端合并变更;客户端要处理超时重发

四种机制对应四种工程习惯:

  • etcd:list + watch + revision,把 watch 当「增量提示」;
  • Consul:blocking query,本质是「带 index 的长轮询」,没有推送;
  • ZooKeeper:watch 触发后必须重新注册,注册间隙要重新读一次;
  • Nacos:长轮询 + MD5,服务端 hold 住请求,变更才返回。

机制细节与「断线后从哪续」的通用模式见 48.5 Watch 与通知机制。

选型决策表

你的场景 选 理由
已经在 K8s 上,要配置/选主/锁 etcd(或 K8s Lease) K8s 已经依赖 etcd;少一套组件
多数据中心服务发现 + KV + 健康检查 Consul 原生多 DC、catalog、DNS 接口、mesh 生态
Spring Cloud Alibaba、配置+注册一体、国内团队 Nacos 配置与命名一体、控制台友好、AP 注册
Hadoop/旧 Kafka 生态、已有 Curator ZooKeeper 生态绑定;但 Kafka 新版本已转向 KRaft
只想要一个强一致 KV + watch + lease etcd 模型最小、运维简单、K8s 验证充分
海量实例注册、能接受 AP Nacos / Eureka 心跳 + 本地缓存,分区可用
跨云/跨集群统一服务发现 Consul / Nacos 多 DC/多集群能力;etcd 需自己做
需要存大对象/高吞吐数据 数据库/对象存储 协调服务不是数据面

配置中心的能力与推拉权衡见 48.1 配置中心;注册、健康检查与摘除见 48.2 服务发现。

多数据中心:最容易低估的差异

产品 多 DC 能力 代价
etcd 无原生多 DC;常见做法是每 DC 一个集群 + 上层同步/mirror 跨集群一致性要自己做
Consul 原生多 DC:每 DC 一组 server,WAN gossip 互联 跨 DC 查询延迟;WAN 故障要处理
ZooKeeper observer 跨 DC 读;写仍在主 DC 写延迟;observer 读可能更陈旧
Nacos 集群 + AP 注册,跨集群同步 跨集群数据一致性弱;要接受 AP

选多 DC 方案时先问:写要不要跨 DC? 要,就要接受 RTT;不要,就做「本地写 + 异步复制」,并接受冲突/陈旧。

容量与性能:量级,不是跑分

维度 影响因素 工程含义
写吞吐 共识落盘 + quorum RTT + 批量 协调服务写吞吐远低于业务数据库;不要拿它存流水
读吞吐 是否走 leader/quorum 本地读快但可能陈旧;线性一致读要付 RTT
Watch 规模 watcher 数 × 监听范围 × 事件速率 内存与网络的主要成本;慢 watcher 要能断开
会话/租约 实例数 × 心跳频率 心跳 QPS = 实例数 / 周期;TTL 决定误摘率
存储 value 大小 × key 数 + 历史 revision 小 value、少历史;大对象放对象存储

具体 QPS 取决于硬件、value 大小、批量与磁盘;不要用别人的跑分做容量规划,用自己的 value 大小和一致性要求压测。

运维与故障处理

事项 etcd Consul ZooKeeper Nacos
备份 snapshot snapshot snapshot + transaction log 数据库备份(外置 MySQL)
扩容 加成员/learner 加 server/agent 加 server/observer 加节点
常见故障 磁盘慢导致 leader 抖动 gossip/WAN 分区 会话超时、磁盘慢 数据库/长轮询堆积
关键监控 leader 变更、fsync 延迟、watch 数 serf/WAN、catalog 延迟 会话数、watch 数、磁盘 长轮询连接、配置推送延迟

上线配置:etcd 三节点

下面是三节点模板。本机用单节点 + TLS 跑通了 endpoint health、status、put/get 与 snapshot save;三节点只是 initial-cluster 不同。

# /etc/etcd/etcd.conf.yml(每个节点只改 name 与 IP)
name: etcd-1
data-dir: /var/lib/etcd
listen-client-urls: https://0.0.0.0:2379
advertise-client-urls: https://10.0.1.11:2379
listen-peer-urls: https://0.0.0.0:2380
initial-advertise-peer-urls: https://10.0.1.11:2380
initial-cluster: etcd-1=https://10.0.1.11:2380,etcd-2=https://10.0.1.12:2380,etcd-3=https://10.0.1.13:2380
initial-cluster-state: new
initial-cluster-token: prod-etcd-2026

client-transport-security:
  cert-file: /etc/etcd/server.crt
  key-file: /etc/etcd/server.key
  client-cert-auth: true
  trusted-ca-file: /etc/etcd/ca.crt
peer-transport-security:
  cert-file: /etc/etcd/peer.crt
  key-file: /etc/etcd/peer.key
  client-cert-auth: true
  trusted-ca-file: /etc/etcd/ca.crt

auto-compaction-retention: "1h"
quota-backend-bytes: 8589934592
heartbeat-interval: 100
election-timeout: 1000
参数 值 依据 配错的后果
成员数 3 或 5 容忍 (n-1)/2 台故障 2 节点:挂 1 台就不可写
initial-cluster-token 每个集群唯一 防止新成员误加入旧集群 复用 token:两个集群串在一起
quota-backend-bytes 8 GiB 默认 2 GiB 量级,生产偏小;超限进入 NOSPACE 只读 不设/设太大:db 写满磁盘,全集群不可用
auto-compaction-retention 1h watch 断线可回放 1 小时;太长则 db 膨胀 不开:历史 revision 无限涨,终会撞 quota
heartbeat-interval / election-timeout 100 / 1000ms 同 AZ 默认够;跨 AZ 要 ≥ 3×RTT 跨 AZ 用默认:leader 反复重选,写超时
max-request-bytes 默认 1.5 MiB 单请求上限;大 value 直接拒绝 调大 = 用共识搬大对象,集群被拖慢
snapshot-count 默认 10 万 每 10 万条做一次快照 太小:频繁 fork,影响写延迟
E="--endpoints=https://10.0.1.11:2379,https://10.0.1.12:2379,https://10.0.1.13:2379"
E="$E --cacert=/etc/etcd/ca.crt --cert=/etc/etcd/client.crt --key=/etc/etcd/client.key"

etcdctl $E endpoint health --cluster
etcdctl $E endpoint status --cluster -w table      # DB SIZE / QUOTA / IS LEADER / RAFT TERM
etcdctl $E member list -w table
etcdctl $E alarm list                              # NOSPACE / CORRUPT

备份与恢复演练(恢复只能到空的 data-dir,且节点先停):

etcdctl $E snapshot save /backup/etcd-$(date +%F-%H%M).db
etcdutl snapshot status /backup/etcd-2026-10-26-1200.db -w table
etcdutl snapshot restore /backup/etcd-2026-10-26-1200.db --data-dir /var/lib/etcd-restore
场景 动作 不要做
单节点故障 Raft 自动选主;客户端配置全 endpoint 手工删 data-dir 重加
超过半数不可用 先把节点恢复,让它自己追 --force-new-cluster(丢历史)
NOSPACE 告警 逐节点 defrag → alarm disarm → 调 quota 或清大 value 只 disarm 不 defrag(很快再触发)
误删 key 只能快照恢复 指望 undo / 回收站

另一条不属于参数的纪律:etcd 只放最终态,变更历史放 Git。etcd 没有“回滚到上一版”的能力,快照恢复是整库回退,代价是回到备份时刻。

常见误区

误区 现实
「ZooKeeper 是强一致」 写线性一致,读可能陈旧;要新鲜读先 sync()
「etcd 可以存大 value」 单 value 有上限,且大 value 会拖慢共识;大对象放对象存储
「Consul KV 默认强一致」 默认可能陈旧;需要时加 ?consistent
「Nacos 只是配置中心」 它同时做服务发现(naming)与配置
「四个产品可以随意替换」 数据模型、watch 语义、会话/租约、多 DC 能力都不同,迁移有成本
「协调服务能当数据库/消息队列」 它是元数据/协调层,不是数据面
「Kafka 还依赖 ZooKeeper」 新版本已用 KRaft 取代 ZooKeeper

反模式

反模式 后果
跨地域部署共识 quorum 还要求低延迟写 写延迟被 RTT 锁死,超时/抖动
用协调服务存业务流水/大对象 磁盘/共识被打满,集群不可用
不看 API 语义,默认读都是最新的 读到旧配置/旧实例列表
watch 数量无上限、慢消费者不处理 内存上涨,服务端被拖垮
选主不做 fencing 双主写(48.4)
不做备份/恢复演练 元数据丢失无法重建
把 AP 注册中心用于强一致锁 分区期间双持有(48.3)

自检

Q1 为什么说「ZooKeeper 是强一致」不准确?

▸ 答案:ZooKeeper 的写是线性一致的(ZAB 全序提交),但客户端可能连到 follower,读默认由本地副本返回,可能读到旧值;它保证的是「客户端 FIFO 顺序 + 写全序」,不是「任意读都最新」。需要新鲜读时先 sync() 再读。类似地,etcd 的 serializable 读、Consul 默认 KV 读也都可能陈旧。

Q2 etcd 和 Consul 都能做服务发现,选哪个?

▸ 答案:看是否需要原生多数据中心与服务发现开箱能力。Consul 原生多 DC、catalog、健康检查、DNS 接口,适合跨 DC/跨云的服务发现;etcd 只是一个强一致 KV + watch,服务发现要自己封装(或依赖 K8s),优势是与 K8s 同栈、组件少、运维简单。如果已经在 K8s 内,优先用 K8s Service/EndpointSlice;需要跨集群/非 K8s,再看 Consul/Nacos。

Q3 Nacos 的 AP/CP 是怎么切的?什么时候会踩坑?

▸ 答案:Nacos 的临时实例(ephemeral)走 Distro 协议(AP),靠心跳保活,分区时仍可注册/发现;持久实例(persistent)走 Raft(CP),强一致但分区时少数派不可用。踩坑场景:把需要强一致的锁/选主建立在临时实例上,或把高频变化的服务注册放到持久实例上——前者会在分区时双持有,后者会把 Raft 写打满。选之前先明确「这条数据要 AP 还是 CP」。

Q4 为什么不能用 etcd/Consul 的 KV 存业务大对象?

▸ 答案:协调服务的写要走共识(落盘 + 多数派),value 越大,每次写的网络/磁盘成本越高,还会拖慢整个集群的提交;同时 etcd 保留历史 revision、Consul 有 value 大小限制,大对象会迅速放大存储与快照成本。大对象应放对象存储/数据库,协调服务只存「指针/元数据」。

Q5 从 ZooKeeper 迁到 etcd,最容易忽略的语义差异是什么?

▸ 答案:① watch 模型:ZK 标准 watch 一次性,etcd 是流式 + revision 回放;迁移后要重写「重注册/补偿」逻辑。② 读一致性:ZK 读可能陈旧、需要 sync();etcd 默认线性一致读,延迟更高,要显式选 serializable。③ 临时节点 vs lease:ZK 会话超时 vs etcd lease keepalive,失效检测时间与客户端行为不同。④ 数据模型:树形 znode vs 扁平 KV + 前缀,路径/权限/通知范围都要重新设计。迁移前先做语义对照表,不要只做 API 映射。

Q6 etcd 报 NOSPACE 后为什么所有写都失败?正确的恢复顺序是什么?

▸ 答案:db 超过 quota-backend-bytes 后 etcd 进入只读告警,防止把磁盘写满导致崩溃——这是一种保护,不是 bug。恢复顺序:① 处理大 value / 清理无用 key;② 逐节点 etcdctl defrag(会阻塞该节点,滚动做);③ etcdctl alarm disarm 解除告警;④ 必要时调大 quota 或扩盘。只 disarm 不 defrag 会很快再次触发;根治是开 auto-compaction-retention 并把大对象移出 etcd。

关联