Chapter 04

一致性、选型与生产

前两章分别讲了临时实例的 AP 传播(Distro)和配置的近实时下发。这一章把 AP 和 CP 合到一张图上, 讲清那条脊柱到底怎么分流、横向和 Eureka / Consul / ZooKeeper / Apollo 怎么选、以及上生产前要拧紧哪些螺丝。

本章你将建立的 schema

  • 双协议如何按 ephemeral 分流——起点页那条脊柱在这里完整收口。
  • 为什么注册中心默认选 AP,而 ZooKeeper 的 CP 反而不适合做注册中心。
  • 网络分区时,AP 侧和 CP 侧分别会发生什么。
  • 注册中心 vs Eureka/Consul/ZK、配置中心 vs Apollo 的选型依据。
  • 生产前必须确认的几个默认值:protect-threshold、端口、版本兼容、JVM。

4.1双协议分流:脊柱收口

Nacos 把 Distro(AP)和 Raft(CP)藏在统一的一致性接口后面,按数据的 ephemeral 字段路由——临时实例走 AP,持久实例和配置走 CP。

到这里,起点页那张概念图可以补全了。一次写入进 Nacos,先看它是什么数据,再决定走哪套协议:

客户端 SDK gRPC 长连接 Nacos 服务端 接入层 · gRPC / HTTP / OpenAPI 按 ephemeral 路由 Distro · AP 异步复制 · 内存 Raft · CP 多数派提交 · 磁盘 临时实例(仅内存) 持久实例 + 配置(MySQL) true false / 配置
图 4.1同一个接入层、同一套数据模型,在"路由"节点后分成两套协议、两种存储。 注意:左右两条路径除了协议名,连存储介质都不同(内存 vs 磁盘/MySQL)。这不是两个产品拼在一起,是一个产品按数据类型给出两种一致性承诺。
为什么注册中心默认选 AP(关键论断)

注册中心的使用者能容忍"服务列表稍微过时"——查到一个刚下线的实例,调用失败后重试别的就行。但不能容忍"网络分区时无法注册、无法查询"——那等于整个服务网格瘫痪。所以注册中心应当可用性优先于一致性。这正是 Eureka 团队当年反对用 ZooKeeper 做注册中心的核心论点。

ZooKeeper 是 CP(ZAB 协议):网络分区时,少数派那一侧直接不可用;而且 Leader 挂掉重新选主期间(可达秒级甚至更久),整个集群拒绝写入。对一个"随时有实例上下线要登记"的注册中心,这种停顿是灾难。Nacos 把临时实例放在 AP(Distro)正是这个权衡:宁可列表短暂不一致,也要分区时还能注册和查询。而配置、持久实例这些"错一点就出事"的数据,才放进 CP(Raft)。

4.2CAP 取舍的具体后果

同一次网络分区,AP 侧(Distro)两边都继续服务、容忍短暂不一致,CP 侧(Raft)只有多数派能写。

抽象的 CAP 落到 Nacos 上很具体。设想一个 5 节点集群被网络分区切成 3 + 2 两块:

表 4.1 · 5 节点被分区成 3+2 时
数据3 那一侧2 那一侧分区恢复后
临时实例(AP·Distro)继续注册 / 查询也继续注册 / 查询异步收敛,最终一致
持久实例 + 配置(CP·Raft)有多数派,可读可写无多数派,不可写本就一致,2 侧追平日志
洞察 · "可切换"到底是什么意思

很多人把"Nacos 支持 AP/CP 切换"理解成一个全局开关,调一下整个 Nacos 变 CP。错。它是按数据逐条选的:同一时刻、同一个集群里,你的临时实例在走 AP、你的配置在走 CP。理解这一点,上面这张分区表里"为什么同一次分区两类数据命运不同"就不再奇怪——因为它们从注册那一刻起就走了不同的协议。

4.3横向选型:和谁比、怎么选

注册中心看一致性模型与生态,配置中心看治理能力;Nacos 的独特卖点是"注册 + 配置二合一 + AP/CP 按数据选"。

表 4.2 · 注册中心选型
方案一致性健康检查 / 特点怎么选
NacosAP 默认 / CP 可选(按 ephemeral)心跳 + 服务端探活;注册 + 配置二合一Java / Spring Cloud Alibaba 生态首选
EurekaAP(自我保护机制)客户端心跳;2.x 开源已停维护遗留系统在用,新项目不建议
ConsulCP(Raft)健康检查强;多数据中心 / 服务网格多 DC、Mesh、非 Java 多语言
ZooKeeperCP(ZAB)会话 / 临时节点协调服务可以;做注册中心不合适(见 §4.1)
表 4.3 · 配置中心选型
方案特点怎么选
Nacos注册 + 配置二合一;长轮询 / gRPC 推送;灰度已用 Nacos 做注册,配置顺手一体化
Apollo治理强:权限 / 审计 / 三层模型 / 双 DB / 灰度完善需要细粒度权限与审计治理时更胜一筹
Spring Cloud ConfigGit 存储,需 /refresh 或 Spring Cloud Bus 推送实时性差、依赖多,渐被取代
Consul KV / etcd通用 K/V,无配置中心 UI / 灰度已重度使用它们、愿自建配置体验时

反过来说,什么时候不该选 Nacos:需要企业级细粒度权限和审计治理 → Apollo 更成熟;多数据中心或服务网格 mTLS → Consul;Kubernetes 原生的强一致 K/V → etcd;纯非 Java 多语言栈、又不吃 Spring Cloud Alibaba 红利 → Consul / etcd 更顺。

注册中心选型 可用性优先? 多 DC / 网格? 否·要 CP SCA 生态? 是·AP Consul 是 ZooKeeper 遗留 否 Nacos 是 Consul / etcd 否
图 4.2注册中心选型从一个问题开始:可用性优先还是强一致。 注意:右下角 Nacos 是 Java/SCA 生态 + 可用性优先的交点;ZooKeeper 落在"要 CP 但又没有多 DC 需求"的角落——这恰恰是注册中心最不该待的位置。

4.4生产前要拧紧的螺丝

Nacos 的几个默认值是为"开箱即用"调的,不是为生产调的——上线前逐个确认。

表 4.4 · 默认值 → 生产建议
项默认风险 / 建议
protect-threshold0= 永不保护。应设 0.x:健康占比过低时返回全部实例,避免流量全压到少数健康实例引发雪崩
心跳间隔 / 超时5s / 15s / 30s上下线偏慢,可调 preserved.heart.beat.interval 等加快
JVM 堆单机 512m按服务 / 配置规模上调,配 G1
端口88482.x 还需放通 9848 / 9849(主端口 +1000 / +1001)
存储内嵌 Derby集群必须外置 MySQL,并给 DB 做高可用
比文档深一层 · protect-threshold 怎么工作

保护阈值是"健康实例占总实例的比例"。当健康占比低于阈值,Nacos 不再只返回健康实例,而是返回全部实例(含不健康的)。逻辑是:与其把全部流量挤到剩下的少数健康实例上、把它们也压垮(雪崩),不如把流量摊给所有实例、赌一部分"标记不健康"的其实还能处理。默认 0 意味着这层保护从不触发——名字像开了保护,实则裸奔。

陷阱 · 版本兼容是单向的

2.x 服务端兼容 1.x 客户端;但 2.x 客户端连不上 1.x 服务端(它要走 gRPC)。所以升级顺序必须是先升服务端、再升客户端,反了直接连不上。

4.5失败模式:把前四章接起来

线上最高频的几类故障,根因都能回指到前面的某个机制——排障就是反向走这条链。

先看最经典的一条因果链——"实例已死,调用方还在打它",它正是 §1.4 的剔除时间线和 §2.4 的客户端缓存叠加出来的:

① 实例崩溃 kill -9 ② 延迟剔除 15s / 30s ③ 缓存滞后 客户端列表未刷新 ④ 打到死实例 超时 / 抖动 缓解:调用方重试 + 缩短客户端刷新 + 合理 protect-threshold
图 4.3四步因果链:每一步单独看都"正常",叠起来就成了抖动。 注意:缓解手段不在某一步堵死,而是分散在调用方(重试)、客户端(刷新频率)、服务端(保护阈值)三处——单靠调心跳治不好。
表 4.5 · 高频失败模式速查(排障反向走机制)
症状根因(回指章节)对策
调用到已下线实例、抖动剔除延迟 + 客户端缓存(§1.4 / §2.4)重试 + 缩短刷新 + protect-threshold
扩缩容瞬间列表忽有忽无Distro owner 重新分配(§2.1)低峰分批扩缩容
发现不到服务 / 读不到配置namespace 填了名字非 UUID(§1.2 / §3.3)核对 namespace 的 UUID、group
2.x 客户端连不上、8848 却通9848 / 9849 未放通(§2.3)防火墙放通 +1000 / +1001 端口
配置改了不生效缺 @RefreshScope(§3.3)加 @RefreshScope 或用 @ConfigurationProperties
想一想

2.x 升级后,一个服务死活注册不上,启动日志反复刷连接失败,但你 telnet 8848 是通的。最可能是什么原因?

展开答案(先停 10 秒)

最可能是 9848 端口没放通。2.x 客户端注册走 gRPC,端口是主端口 +1000(即 9848);防火墙只开了 8848 时,HTTP 探测看着是通的,但 gRPC 连不上,于是注册失败。放通 9848 / 9849 即可。这是把 §2.3 的端口知识用在排障上。

§本章 self-check

先合上教程作答,再展开对照。

  1. 一句话:Nacos 的"AP/CP 可切换",切换的是什么粒度?是全局开关吗?
  2. 为什么注册中心通常选 AP 而非 CP?用 ZooKeeper 分区 / 选主时的行为来说明。
  3. 5 节点集群被分区成 3+2,临时实例(AP)和持久实例 / 配置(CP)分别会发生什么?
  4. protect-threshold=0 意味着什么?为什么生产上危险?它触发后 Nacos 返回什么?
  5. (综合)2.x 升级后客户端连不上、但 8848 是通的,最可能的原因?升级顺序应该是服务端先还是客户端先?
答案(先做完再展开)
  1. 切换的是单条数据的粒度,由 ephemeral 决定:临时实例 AP、持久实例 / 配置 CP。不是全局开关,同一集群两套协议同时在跑。
  2. 注册中心要可用性优先:能容忍列表稍旧(重试别的实例),但不能容忍分区时无法注册 / 查询。ZooKeeper 是 CP,分区时少数派不可用、选主期间整个集群拒绝写——对注册中心是灾难。
  3. 临时实例:3 和 2 两侧都继续注册 / 查询,可能短暂不一致,恢复后异步收敛。持久实例 / 配置:3 那侧有多数派可写,2 那侧无多数派不可写。
  4. = 永不触发保护。危险在于:当健康实例很少时仍只把流量发给健康的,可能把它们也压垮(雪崩)。触发后 Nacos 返回全部实例(含不健康),把流量摊开。生产设 0.x。
  5. 最可能 9848 端口未放通(2.x gRPC = 主端口 +1000)。升级顺序:先服务端、后客户端,因为 2.x 客户端连不了 1.x 服务端。
进阶挑战 · 刚好够不着

"既要注册中心高可用,又要配置强一致"

架构评审上有人质疑:注册中心要 AP 高可用、配置中心要 CP 强一致,这是矛盾的两个要求,得上两套中间件吧?用本教程的脊柱回应他——一套 Nacos 能不能同时满足?为什么?

提示(卡住再展开)

能,且这正是 Nacos 的设计初衷。AP / CP 不是集群级开关,而是按数据类型选(§4.1):临时实例走 Distro/AP(高可用),配置走 Raft/CP(强一致),两者在同一个集群里同时提供。所以"高可用的注册 + 强一致的配置"不矛盾——它们本来就走在两条不同的协议路径上。把图 4.1 画给他看,比说一百句都管用。