主题
高可用与容量规划
一句话:Nacos 的高可用不是「集群起来了就没事」,而是三件事同时成立:接入层能把长连接送到节点、客户端本地缓存能在全挂时兜底、容量能扛住「连接数 × 节点数」和「变更数 × 订阅数」的放大。
适用版本:Nacos 3.2.4;2.5.4 的差异单独标注。接入层与监控指标的具体名称随云厂商/LB 产品不同,以实际环境为准。
为什么需要
注册中心在调用链之外,但它的故障会以「调用方拿不到实例」的形式出现。更麻烦的是,Nacos 往往不是被自身压垮,而是被接入层配置、客户端重连、配置推送放大这三件事联合压垮:
- 只放通 8848,SDK 的 9848 长连接建不起来。
- LB 的空闲超时短于客户端保活间隔,连接被反复断开,重连风暴打满服务端。
- 一次配置变更推给所有订阅者,实例越多,瞬时推送量越大。
- 全挂后新实例起不来,因为本地既没有快照也没有可用节点。
核心机制
两种接入方式,别混
| 方式 | 做法 | 适用 | 关键约束 |
|---|---|---|---|
| 客户端直连多地址 | server-addr 填 3 个节点地址 | 中小规模、能维护地址列表 | 客户端自己轮询/故障切换 |
| VIP / SLB | 一个域名或 VIP 指向多节点 | 大规模、地址常变 | 必须四层 TCP 转发 8848/9848/9849;不能只做 HTTP |
| K8s Service / Ingress | Service 暴露 TCP,Ingress 只给控制台/API | 云原生环境 | gRPC 不能走普通 HTTP Ingress,需 TCP 透传或专门网关 |
控制台 8080 与客户端 9848 是两条完全不同的路径。把控制台挂到 Ingress 上能打开页面,不代表 SDK 能连上;反过来,SDK 能注册也不代表运维能打开控制台。排障时先确认「是哪条路径不通」。
故障雪崩是怎么形成的
这张图的重点是回路:连接断开不是一次性事件,它会通过「实例被摘除 → 流量集中 → 更多实例过载」形成正反馈。处置时先切断回路(修 LB 超时/放通端口/给重连加抖动),再谈扩容;直接重启 Nacos 往往会让所有客户端同时重连,把雪崩推得更高。
全挂时到底还能不能调用
| 能力 | Nacos 全挂时的表现 | 前提 |
|---|---|---|
| 已启动的 Consumer 调用已有实例 | 可以,按本地缓存直连 | 缓存未被清空,实例仍存活 |
| 新启动的 Consumer 调用 | 通常可以,从磁盘缓存/快照恢复 | 之前落过盘,且没有禁用本地缓存 |
| 发现新注册的实例 | 不可以 | 需要注册中心可达 |
| 修改配置并生效 | 不可以 | 需要服务端与推送链路 |
| 控制台运维 | 不可以 | 8080 不可达 |
所以「Nacos 全挂,业务全挂」不准确;准确的说法是「已运行的无状态服务还能撑一段,但失去弹性和变更能力」。真正危险的是新实例启动:既连不上服务端,又没有本地快照,就会启动失败。
容量测算:四个乘数
不要背某个「单机支持多少连接」的数字,按自己的环境算:
- 长连接数 ≈ 客户端进程数 × 节点数 × 连接模块数。命名和配置可能各自维护连接,具体以客户端实现为准;3 节点、1000 个客户端进程,量级就是几千条,不是 1000 条。
- 注册/心跳 QPS ≈ 实例数 ÷ 心跳间隔。临时实例越多,服务端连接与心跳处理越重;心跳间隔调小会线性放大。
- 推送放大 ≈ 变更次数 × 订阅实例数。一次配置发布推给 500 个实例,就是 500 次通知;频繁发布会把推送和客户端拉取打成热点。
- MySQL 压力 ≈ 配置发布次数 + 历史写入 + 启动期 dump。配置读大多走本地缓存,但发布、历史、灰度、启动 dump 都会打 DB。
容量评估的顺序是:先压测单节点,得到「本环境」的连接数、推送延迟、CPU/内存曲线,再按节点数做线性外推并留余量;不要拿别人的 benchmark 当保证值。
监控什么
- 连接面:gRPC 连接数、连接建立/断开速率、连接数与前缀客户端数是否匹配。
- 注册面:实例总数、健康/不健康数、心跳超时数、持久实例 leader 切换。
- 配置面:发布次数、推送失败数、客户端拉取失败、dump 失败、灰度版本数。
- 资源面:JVM 堆/GC、直接内存、文件句柄、CPU、磁盘;MySQL 慢查询、连接数、主从延迟。
- 业务面:调用方「无可用实例」错误率、注册后首次可调用延迟。
指标名随版本和监控体系变化,以实际暴露为准;关键是这五类都要有,不能只看 CPU。
分层指标、告警分级和排障顺序见 08-监控指标与告警。
备份与恢复
- 配置真相在 MySQL:定期全量备份 + binlog;恢复演练要包含「只恢复配置表」和「整库恢复」两种。
- 节点本地快照不是备份:它只是缓存,节点重建后会重新 dump。
- 命名空间/分组清单:导出配置列表,恢复后核对数量与关键 dataId。
- 恢复顺序:先 MySQL,再 Nacos 节点,最后客户端;不要在 DB 还没恢复时启动节点,否则会 dump 出不完整数据。
多集群/多活的边界
Nacos 没有原生跨数据中心复制。常见做法是每个机房一套集群,客户端优先连本地,故障时切远端;或引入 Nacos-Sync 做单向/双向同步。代价是:
- 双向同步会有冲突,注册数据尤其难合并。
- 跨机房延迟会让「推送即时性」下降。
- 两套集群的配置版本可能不一致,回滚要分别做。
如果业务要求机房级自动切换,先把「注册数据能否接受最终一致」说清楚,再选方案。
实战场景
一、LB 空闲超时把长连接切了:夜间周期性雪崩
现象:接入 VIP 后,每天凌晨 2 点到 4 点反复出现同一模式:一批实例被标记不健康并从列表剔除,业务侧表现为调用超时;半小时后自愈,第二天再来一次。Nacos 节点本身 CPU、内存、GC 都正常。
排查:连接断开速率是最先要看的曲线,它比实例数变化更早。
| 定位层 | 取证据 | 判定 |
|---|---|---|
| 连接断开速率 | 服务端 gRPC 连接数曲线、断开/建立速率 | 与“实例被剔除”时间点对齐 → 断连是源头 |
| LB 空闲超时 | 负载均衡设备的 idle timeout 配置(常见默认 60s) | 客户端约 5s 保活,但 VIP 侧超时更短时仍会被切 |
| 转发层级 | 9848 是否四层 TCP 转发 | 用七层 HTTP LB 转 gRPC 会在帧/超时层面出问题 |
| 端口放通 | 9848/9849 在安全组是否全通 | 只放 8848 时表现为 Client not connected, current status: STARTING |
| 低峰因素 | 夜间是否有批量任务导致客户端静默、连接被判定空闲 | 空闲期越长越容易被切 |
| 回路是否存在 | “断开 → 重连 → 心跳丢失 → 剔除 → 流量集中 → 更多超时”的时间序列 | 形成正反馈即为雪崩而非单点故障 |
对症做法:
text
1. 9848/9849 用四层 TCP 转发;空闲超时必须大于客户端保活间隔并留余量,
具体值按设备与压测定,不要用默认 60s 赌客户端行为。
2. 客户端重连加退避与 jitter,避免整批同时重建连接。
3. 服务端设置连接上限与 accept 保护,防止风暴把节点打穿。
4. 处置顺序:先切断回路(改 LB 超时/放通端口/加抖动),再谈扩容;
直接重启 Nacos 会让所有客户端同时重连,把雪崩推得更高。bash
# 快速判断“是被中间设备切的,还是服务端主动关的”
# 服务端日志无 close 记录 + 客户端报 connection reset → 中间层
# 服务端有 idle/limit 相关日志 → 服务端复盘要点:判据是“连接断开速率与实例剔除率的相关性”,两者要能在同一时间轴上对上。接入方式要在上线清单里明确写出“9848 走四层、idle timeout > 保活间隔 × N”,而不是记录成“已接 VIP”。
二、控制台能打开,SDK 连不上:两条路径混成一条
现象:在 K8s 里部署 Nacos,用 Ingress 暴露 8848 和 8080。运维能登录控制台、curl OpenAPI 也正常,但业务 Pod 一直报注册超时。有人据此认为“Nacos 没问题,是应用的问题”。
排查:控制台 8080 与客户端 9848 是两条完全不同的路径。
| 定位层 | 取证据 | 判定 |
|---|---|---|
| SDK 实际连的端口 | 客户端日志里的目标地址;在 Pod 内 nc -vz nacos 9848 | 9848 不通即注册与推送都不工作 |
| Ingress 类型 | 是否只有 HTTP Ingress | 普通 HTTP Ingress 不透传 gRPC 长连接 |
| 后端暴露方式 | Service 是否为 type: LoadBalancer/NodePort 且带 TCP 端口 | ClusterIP + Ingress 无法承载集群外 SDK |
| 集群内/外 | 同集群内应用能连、跨集群不能连 | 是路径问题,不是 Nacos 版本问题 |
| 控制台判据 | 8080 页面能开 | 与 SDK 可用性无关,不能作为健康判据 |
对症做法:
yaml
# Nacos Service 同时暴露三个 TCP 端口;gRPC 端口不做 HTTP 映射
apiVersion: v1
kind: Service
metadata: { name: nacos }
spec:
type: LoadBalancer # 或 NodePort + 固定端口
ports:
- { name: http, port: 8848, targetPort: 8848 }
- { name: grpc-client, port: 9848, targetPort: 9848 } # SDK 数据面
- { name: grpc-cluster, port: 9849, targetPort: 9849 } # 节点间yaml
# 控制台单独走 Ingress,健康检查指向 8848 的健康端点或 TCP,不检查 8080
readinessProbe: { tcpSocket: { port: 8848 } }text
1. 接入方式三选一要先定:客户端直连多地址 / VIP 四层 / K8s Service TCP + Ingress 只管控制台。
2. 上线验收要分别验证两条路径:SDK 注册成功(9848)+ 控制台可登录(8080);
只验一条会得出相反的错误结论。复盘要点:这条判据值得贴在读手册首页——“HTTP 通”不能作为 Nacos 可用的判据。健康检查、告警、上线验收都要按端口路径分开表达,否则一次 Ingress 变更就能伪装成“Nacos 挂了”。
三、实例数翻倍后 Too many open files:容量按乘数算
现象:接入应用从 400 个进程增到 900 个,实例总数约 2600。扩容完成后第三天,Nacos 节点报 Too many open files,部分客户端注册失败,心跳处理延迟上升;单节点 CPU 只有 45%。
排查:连接量不是客户端数,按乘数算才知道差距。
| 乘数 | 计算式 | 本例数值(示例口径,需按实现核实) |
|---|---|---|
| 长连接数 | 客户端进程数 × 节点数 × 连接模块数 | 900 × 3 × 1 ≈ 2700 条,而不是 900 |
| 注册/心跳 QPS | 实例数 ÷ 心跳间隔 | 2600 ÷ 5s ≈ 520 QPS,调小心跳会线性放大 |
| 推送放大 | 变更次数 × 订阅实例数 | 一次配置发布推约 900 个客户端 |
| MySQL 压力 | 发布次数 + 历史写入 + 启动期 dump | 批量重启时 dump 会集中打库 |
| 定位层 | 取证据 | 判定 |
|---|---|---|
| 句柄使用 | cat /proc/\<pid\>/limits、lsof -p <pid> 计数、ulimit -n | 接近上限即命中 |
| 直接内存 | -XX:MaxDirectMemorySize 与实际使用;gRPC/Netty 堆外占用 | 容器 limit 不足会被 OOM kill |
| 连接数与前缀客户端数 | 服务端连接数 vs 客户端进程数 | 比值接近节点数即符合乘数模型 |
| 单节点基线 | 本环境压测得到的连接数/推送延迟/CPU 曲线 | 没有基线就无法判断“还剩多少余量” |
对症做法:
bash
# 1. 长连接服务必须显式抬高句柄与堆外预算,并确认容器 limit 足够
ulimit -n 65535 # systemd/Deployment 里同步设置,不要只改 shell
JAVA_OPT="-XX:MaxDirectMemorySize=1g"yaml
# 2. 容器侧 limit 要覆盖堆外 + 句柄相关内核开销
resources:
requests: { memory: "2Gi" }
limits: { memory: "3Gi" }text
3. 容量流程固定为:先压测单节点 → 得到连接数、推送延迟、CPU/内存曲线
→ 按节点数线性外推并留余量(例如按 70% 水位规划,经验值)
→ 再决定加节点还是拆分集群;不要拿别人的 benchmark 当保证值。
4. 重连风暴要有服务端保护:连接上限、accept 限速、客户端 jitter。
5. 配置发布按批:一次发布推 900 个客户端是常态开销,
频繁发布会把推送与客户端回拉打成热点,见 [配置中心](03-配置中心.md)。复盘要点:判据是“距离单节点压测基线的余量”,指标侧看连接数、连接建立/断开速率、心跳超时数、文件句柄与直接内存;业务侧看调用方“无可用实例”错误率与注册后首次可调用延迟。也要说清底线:Nacos 全挂时已运行的无状态服务还能按本地缓存撑一段,真正危险的是新实例启动——既连不上服务端又没有本地快照就会失败。备份要按“MySQL 是配置真相”来做,节点本地快照不是备份,恢复顺序是先 MySQL、再节点、最后客户端。
最小上手
先验证接入层,而不是先看 Nacos 日志:
bash
# 三个端口都必须通;9848/9849 是 TCP,不是 HTTP
nc -vz nacos.example.com 8848
nc -vz nacos.example.com 9848
nc -vz nacos.example.com 9849
# 3.x readiness;token 从登录接口获取,不要检查 8080 控制台
curl -s -o /dev/null -w "%{http_code}\n" \
-H "accessToken: $ACCESS_TOKEN" "http://nacos.example.com:8848/nacos/v3/admin/core/state/readiness"
# 客户端缓存目录(路径随客户端版本变化)
ls -la ~/nacos/naming ~/nacos/config再做一次「全挂演练」:停掉 Nacos,确认已启动的 Consumer 仍能调用;重启一个 Consumer,确认它能从本地快照启动。两项都通过,才说明缓存兜底真的有效。
生产动作
- 接入层四层转发:8848/9848/9849 走 TCP;Ingress 只承载控制台/API,不承载 SDK 长连接。
- LB 空闲超时大于客户端保活间隔:客户端约 5s 保活,LB 超时要有余量;具体值按产品与压测定,不要用默认的 60s 赌。
- 健康检查指向服务端:检查 8848 的健康端点或 TCP 端口,不要检查 8080 控制台;控制台挂了不代表注册不可用。
- 文件句柄与直接内存:长连接服务要显式调大
ulimit -n和-XX:MaxDirectMemorySize,并确认容器 limit 足够。 - 重连加抖动:客户端重连要有退避与 jitter;服务端要有连接上限/accept 保护。
- 容量按乘数算:先压测单节点,再按「客户端 × 节点 × 模块」估连接,按「变更 × 订阅」估推送。
- 备份演练:每季度做一次 MySQL 恢复 + 配置核对,不要只备份不恢复。
边界
- 缓存不是免死金牌:本地缓存能让已运行的服务撑一段,但不能发现新实例、不能改配置;新实例启动仍可能失败。
- 多节点不等于高可用:节点同机架、同交换机、同 MySQL,故障域没有分开。
- 控制台高可用是另一件事:3.x 控制台可独立部署,控制台挂了不影响 SDK;不要把控制台探针和客户端探针混用。
- 没有原生多活:跨机房同步要么接受最终一致,要么自己做路由与冲突处理。
- 扩容不能解决所有雪崩:如果是 LB 超时或端口没放通,加节点只会让更多节点一起被重连打满。
讲清楚
Nacos 的高可用要同时满足三件事:接入层把 8848/9848/9849 按 TCP 送到节点,客户端本地缓存能在全挂时兜底,容量能扛住连接和推送的放大。最常见的雪崩不是 Nacos 自己崩,而是 LB 空闲超时或 9848 没放通导致长连接断开,重连风暴让心跳丢失、实例被摘,流量集中又把剩余实例打挂,形成正反馈;处置要先切断回路,再扩容。全挂时已运行的 Consumer 还能按缓存直连,但发现不了新实例、改不了配置,新实例也可能因为既连不上又没快照而启动失败。容量不要背单机数字,按「客户端进程数 × 节点数 × 连接模块数」估连接,按「变更次数 × 订阅实例数」估推送,先压测再外推。它的边界是:没有原生多活,缓存不是免死金牌,控制台高可用也不等于 SDK 高可用。