跳到正文

高可用与容量规划 ​

一句话: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 / IngressService 暴露 TCP,Ingress 只给控制台/API云原生环境gRPC 不能走普通 HTTP Ingress,需 TCP 透传或专门网关

控制台 8080 与客户端 9848 是两条完全不同的路径。把控制台挂到 Ingress 上能打开页面,不代表 SDK 能连上;反过来,SDK 能注册也不代表运维能打开控制台。排障时先确认「是哪条路径不通」。

故障雪崩是怎么形成的 ​

这张图的重点是回路:连接断开不是一次性事件,它会通过「实例被摘除 → 流量集中 → 更多实例过载」形成正反馈。处置时先切断回路(修 LB 超时/放通端口/给重连加抖动),再谈扩容;直接重启 Nacos 往往会让所有客户端同时重连,把雪崩推得更高。

全挂时到底还能不能调用 ​

能力Nacos 全挂时的表现前提
已启动的 Consumer 调用已有实例可以,按本地缓存直连缓存未被清空,实例仍存活
新启动的 Consumer 调用通常可以,从磁盘缓存/快照恢复之前落过盘,且没有禁用本地缓存
发现新注册的实例不可以需要注册中心可达
修改配置并生效不可以需要服务端与推送链路
控制台运维不可以8080 不可达

所以「Nacos 全挂,业务全挂」不准确;准确的说法是「已运行的无状态服务还能撑一段,但失去弹性和变更能力」。真正危险的是新实例启动:既连不上服务端,又没有本地快照,就会启动失败。

容量测算:四个乘数 ​

不要背某个「单机支持多少连接」的数字,按自己的环境算:

  1. 长连接数 ≈ 客户端进程数 × 节点数 × 连接模块数。命名和配置可能各自维护连接,具体以客户端实现为准;3 节点、1000 个客户端进程,量级就是几千条,不是 1000 条。
  2. 注册/心跳 QPS ≈ 实例数 ÷ 心跳间隔。临时实例越多,服务端连接与心跳处理越重;心跳间隔调小会线性放大。
  3. 推送放大 ≈ 变更次数 × 订阅实例数。一次配置发布推给 500 个实例,就是 500 次通知;频繁发布会把推送和客户端拉取打成热点。
  4. 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 98489848 不通即注册与推送都不工作
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 高可用。

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