Zookeeper 是 CP 的还是 AP 的?

#中间件#面试八股

Zookeeper 是 CP 的还是 AP 的?

结论先行:ZK 是典型的 CP 系统。写操作必须在过半节点(quorum)确认后才返回,网络分区导致多数派无法形成时,少数派分区会拒绝服务——牺牲可用性(A)来保一致性与分区容忍(C、P)。读请求允许从 Follower 返回,因此可能出现短暂陈旧读,但它仍属于 CP 语义下的“顺序一致”。

一、quorum 的计算

集群规模多数派可容忍故障说明
110单点,仅开发用
220比 1 台更差,绝不要用
321最小生产规模
532常见生产配置
743再往上写性能下降明显

公式:quorum = n/2 + 1,可容忍故障 = n - quorum = (n-1)/2。偶数节点不增加容错,只增加同步开销。

二、分区时的两种结果

5 节点集群,网络分区成 3 + 2

分区 A(3 台,含 Leader)      分区 B(2 台)
   quorum = 3 ≥ 3 ✔               quorum = 2 < 3 ✘
   继续提供读写                   无法选主,拒绝写
   但 B 侧客户端会话超时           B 侧客户端收到连接失败

这就是“脑裂”不会造成双写的原因:少数派根本凑不齐票数,写提案无法提交。

三、读为什么可能“旧”

ZK 保证的是顺序一致性(Sequential Consistency)与单系统映像,而不是任意时刻的线性一致读。Follower 可能还没应用最新事务就响应读请求:

写 /config=v2 → Leader 提交 → Follower1 已同步,Follower2 未同步
读 /config   → 打到 Follower2 → 返回 v1(陈旧读)

需要读到最新值时用 sync() 把当前连接追赶至 Leader 再读:

client.sync().forPath("/config");
byte[] data = client.getData().forPath("/config");

四、配置与选型

# zoo.cfg(5 节点)
tickTime=2000
initLimit=10
syncLimit=5
server.1=10.0.0.1:2888:3888
server.2=10.0.0.2:2888:3888
server.3=10.0.0.3:2888:3888
server.4=10.0.0.4:2888:3888:observer
server.5=10.0.0.5:2888:3888:observer
组件CAP 取向说明
ZooKeeperCP注册中心场景下宁可短暂不可用,也不要错误地址
EurekaAP节点互相同步,分区时仍可读,接受不一致
Nacos可配临时实例 AP,持久实例 CP

四、为什么注册中心宁可选 CP

取舍后果业务能否接受
选 CP(ZK)分区时少数派不可用,无法注册已运行的调用不受影响,可接受
选 AP(Eureka)分区时仍可读,但可能拿到已下线地址调用直接失败,需重试兜底

注册数据的“正确性”比“随时可写”更重要:错一个地址就是一次线上报错,短暂无法注册只影响新实例上线。

常见追问

  • 追问:那为什么 Dubbo 用 ZK 做注册中心?注册数据变更不频繁,短暂不可用的代价小于拿到错误地址的代价;且消费端有本地缓存,ZK 抖动不影响已有调用。
  • 追问:ZK 是强一致吗?写是线性一致的(经 Leader),读是顺序一致的(可能陈旧),严格说不是全局强一致。
  • 追问:会话超时会怎样?临时节点被删除,注册信息消失,这是 ZK 感知实例下线的机制,也是“假死”误摘除的来源。

小结

  • ZK 用 quorum 换一致性,分区少数派直接停服,因此是 CP。
  • 奇数节点才有意义,5 节点容 2 台。
  • 读可能陈旧,需要最新值时先 sync()。
  • 注册中心选 CP 的代价是短暂不可用,收益是不返回错误地址。
笔记加载中…