跳到正文

定位与整体架构 ​

一句话:Nacos 把「服务名到实例」和「配置键到内容」两类运行时数据放进同一个进程,用 gRPC 长连接把变更推给客户端;换来的是注册配置一体、运维面收敛,代价是两条链路的故障会互相争抢同一份 CPU、内存和数据库。

适用版本:Nacos 3.2.4;2.5.4 仅作兼容对照。3.x 需 Java 17,控制台独立在 8080。Spring 示例为 Spring Cloud Alibaba 2025.0.0.0(Spring Boot 3.5,nacos-client 3.0.3),客户端与服务端版本以官方兼容性说明为准。

为什么需要 ​

没有注册中心时,调用方只能把 ip:port 写进配置:扩一个副本要改配置,换一台机器要改配置,实例挂了列表里还在。没有配置中心时,改一个开关要走「改代码 → 打包 → 发布 → 重启」,回滚同样要发版。

这两件事看起来独立,但工程上高度相似:都是「一小份运行时数据 + 大量客户端订阅 + 变更要尽快到达」。分开部署意味着两套集群、两套鉴权、两套监控、两套备份;放在一起则可以用同一套存储和推送通道,代价是耦合。

Nacos 的选择是:同一个进程,两个模块,两套一致性策略。Naming 和 Config 在代码与数据面上是分开的,不能因为「都在 Nacos 里」就认为它们共享同一套一致性保证。

核心机制 ​

1.x、2.x、3.x 的通信差异 ​

版本命名服务变更通知配置变更通知主要代价
1.xUDP 推送 + 客户端定时拉取兜底HTTP 长轮询,服务端挂起约 30sUDP 不可靠;长轮询占用连接与线程
2.xgRPC 双向流长连接gRPC 双向流长连接端口从 1 个变 3 个,网络与 LB 要放通
3.x同 2.x;3.2 起移除 UDP 推送同 2.x;OpenAPI 不再提供 HTTP 长轮询监听控制台独立、鉴权默认收紧、API 升到 v3

2.x 换 gRPC 的因果链:UDP 会丢包、没有顺序、受 MTU 限制,丢了只能等下一次拉取;HTTP 长轮询每个客户端要挂住一个请求,服务端连接数和线程数随实例数线性上涨。gRPC 一条双向流复用全部注册、订阅、推送,连接级心跳就能判断客户端是否还活着。

端口模型:8848 只是入口 ​

端口用途谁连它
8848HTTP OpenAPI / AdminAPI运维脚本、SDK 的 HTTP 兜底;3.x 控制台不再走这里
9848客户端 gRPC所有 2.x/3.x SDK 客户端
9849集群间 gRPCNacos 节点之间
7848JRaft持久实例的 Raft 复制
8080控制台 UI + ConsoleAPI(3.x 独立)浏览器、控制台前端;路径无 /nacos 前缀

偏移规则是主端口 +1000/+1001、-1000。只放通 8848 时,HTTP 查询能通、控制台可能能开,但客户端会卡在 Client not connected, current status: STARTING,注册和推送都不工作。这是 2.x 升级后最常见的一类「网络没问题但就是用不了」。

图里 8848 是控制面 HTTP 入口,9848 才是客户端真正跑注册、订阅、配置推送的数据通道;9849 只在节点之间用,客户端不会连。防火墙只放通 8848 时,curl 能查到东西,但 SDK 的长连接建不起来,所以「HTTP 通」不能作为 Nacos 可用的判据。

两条链路 ​

同一个进程里跑着两条互不相干的链路:Naming 管「谁在服务」,Config 管「配置是什么」,两边的写入、复制、通知各走各的通道。

共同点是客户端都先持有一份本地数据、再靠长连接感知变化;不同点是 Naming 的一致性由 Distro/Raft 决定,Config 的真相在 MySQL、节点之间同步的只是缓存与通知。

关键判断:配置写失败,通常是 MySQL 或发布链路的问题;注册写失败,通常是 gRPC 连接、Distro 分片或 Raft 多数派的问题。两者不要混着查。

数据模型:四个名字不要混 ​

概念作用默认值易错点
Namespace环境/租户隔离,服务与配置都按它切分3.x 为 public,2.x 为空字符串代码里填名称不填 ID,表现是「读不到但不报错」
Group同一 Namespace 内的业务分组DEFAULT_GROUP注册与配置的 Group 是两套,别以为配了注册就配了配置
Service命名服务的单位无完整坐标是 namespace + group + serviceName
DataId配置文件的身份无SCA 默认用 spring.application.name + 扩展名拼
Cluster同一 Service 下的机房/分区DEFAULT跨机房容灾常被忽略
Instance一个 ip:port 及其元数据weight=1.0、ephemeral=true临时/持久决定一致性协议

与同类组件的取舍 ​

维度Nacos 3.xEureka 1.xConsulZooKeeperApollo
一致性临时实例 AP、持久实例 CPAP + 自我保护CP(Raft)CP(ZAB)配置走 DB,非共识协议
健康检查客户端心跳 + 服务端主动探测客户端心跳Agent 的 TCP/HTTP/TTL 检查Session不涉及实例
配置推送gRPC 长连接无配置中心Blocking Query/WatchWatchHTTP 长轮询(默认 60s 量级)
运维成本3 节点 + MySQL,组件少2 节点即可,功能少3/5 节点 + Gossip,网络要求高3/5 节点,运维经验要求高Portal/Admin/Config 多组件 + DB
多数据中心无原生跨 DC 复制无原生原生 WAN 联邦Observer 跨 DC 读需额外方案
适合Spring Cloud/Dubbo、注册配置一体已有 Netflix 体系多语言、多 DC、服务网格Dubbo/Kafka 等已依赖 ZK 的体系重审批、重灰度、重审计的配置治理

这张表不回答「谁最强」,只回答「你的约束落在哪」:要强一致且能接受少数派不可写,选 CP 型;要注册配置一体且团队小,Nacos 的运维面最少;要跨机房原生复制,Consul 更省事;已有 ZK 生态,不要为了统一而迁移。

实战场景 ​

一、2.x 升 3.x:升级当小版本做,四项同时坏 ​

现象:把 Nacos 从 2.2.3 升到 3.2.4 后,一天内出现四类问题——运维脚本全部 404;应用启动报鉴权失败;某些环境读到空配置;控制台打不开。团队按“小版本升级”预留了 30 分钟窗口,实际回滚用了 4 小时。

排查:3.x 的变更点分散在端口、鉴权、API 和数据模型四处。

变更点2.x 表现3.x 表现现场证据
控制台走 8848,路径带 /nacos独立端口 8080,路径无 /nacos 前缀浏览器访问 8848 控制台地址返回 404
OpenAPI/nacos/v1/... 可直接调v1/v2 剥离到 api-legacy-adapter脚本 404,但客户端 SDK 正常
鉴权默认可关,配置宽松控制台、Admin、Inner API 默认开启鉴权报 unknown user 或 403;三节点 token 不一致时表现为间歇失败
默认 Namespacepublic 用空字符串表示默认 public 为字面量老客户端传空串,新服务端按 public 查,读到空
运行时JDK 8/11 可用需要 Java 17启动即失败或 systemd 单元里 JDK 版本不符
持久实例共识自研 CPJRaft(7848)只放 8848/9848/9849 时持久实例注册异常

对症做法:

bash
# 1. 升级前把“变更面”逐项核对,而不是只换镜像
grep -R "/nacos/v1/\|/nacos/v2/" --include="*.sh" --include="*.py" . # 找出所有 legacy API 调用
# 2. 鉴权三项在所有节点必须一致,且写入配置而非命令行临时传
NACOS_AUTH_ENABLE=true
NACOS_AUTH_TOKEN=<所有节点相同>
NACOS_AUTH_IDENTITY_KEY=serverIdentity
NACOS_AUTH_IDENTITY_VALUE=security
text
3. 分两步走:先把脚本迁到 v3 Admin/Client API 并在 2.x 上验证(装 api-legacy-adapter 保持兼容),
   再升服务端;这样升级日的变更只剩端口与 JDK,而不是四类一起动。
4. 逐节点滚动前先备份 MySQL;滚动过程观察 9849 与 7848 的节点间连通性。
5. 客户端也要核对兼容性:SCA 2025.0.0.0 / nacos-client 3.0.3 与 2.x 服务端的组合以官方兼容表为准,不要凭“能用”判断。

复盘要点:判据是“升级窗口内的变更项数量”——把端口、鉴权、API、Namespace 拆成四次可独立验证的发布,每次都有回滚点。升级演练要覆盖三类真实调用:运维脚本、应用启动、控制台登录;只验证“集群起来了”不足以放行。

二、一次配置大发布把注册中心拖慢:两条链路抢同一份资源 ​

现象:某次批量下发配置(一次改了 200 多个 dataId)之后 1 分钟,多个服务报注册超时、部分实例被剔除(Client not connected),业务侧表现为“服务忽然大面积掉线”。MySQL 同期出现 SELECT ... FROM config_info 的慢查询堆积。

排查:进程合一,就必然争抢 CPU、线程、连接池和推送队列。

定位层取证据判定
慢在哪条链路分别测 Naming 与 Config 的接口耗时;看 gRPC 9848 线程队列只有 Config 慢则问题在配置链路,但影响是共享的
数据库MySQL 慢查询日志、config_info 相关语句、连接使用率批量读写把连接占满 → 注册相关写入排队
通知队列服务端推送线程池队列长度、push 失败计数队列积压 → 心跳与订阅响应被延后
JVMGC 停顿、CPU 使用率与操作时间点停顿与剔除时间对齐 → 心跳超时是 GC 造成
剔除判定15s/30s 阈值与当时服务端处理延迟处理延迟接近阈值就会误判客户端不健康

结论不是“Nacos 有问题”,而是注册与配置共用一套资源时,容量评估要按两者峰值之和算;配置写失败多半在 MySQL 与发布链路,注册写失败多半在 gRPC 连接、Distro 分片或 Raft 多数派,两者不能混着查。

对症做法:

text
1. 批量发布改成分批:每批 ≤ 20 个 dataId,批间间隔 1–2s(经验值,需校准),
   并避开业务高峰与注册风暴时段(服务重启窗口)。
2. 容量隔离优先做进程级:给配置发布单独一组 Nacos 节点(或独立集群),
   命名服务集群只承载注册/订阅流量。
3. 数据库侧:config_info 相关查询走索引确认;连接池上限按“Naming + Config 峰值并发”设,
   并给 MySQL 留出注册链路可用的连接。
4. 剔除阈值不要为“消警”而放大:放大只是把误判变成延迟发现,掉线实例会更久地留在列表里。
properties
# 服务端侧要观察的四个面(写入监控,不靠人盯日志)
grpc_connection_count        # 9848 长连接数:突增通常先于注册失败出现
instance_count               # 实例数:突降说明剔除或网络异常
config_push_fail_count       # 配置推送失败:发布链路的直接指标
jvm_gc_pause / mysql_conn_used

复盘要点:合并部署的收益是运维面收敛,代价是故障半径合并——这条要写进选型说明。判据是“一次配置发布是否影响 Naming 的 P99”:把两者指标放在同一张时间轴上做演练,如果配置发布会把注册链路拖到阈值边缘,就要在容量规划里分组部署,而不是等真实故障验证。

三、用 curl 注册的实例半小时后消失 / 持久实例删不掉 ​

现象:两类相反的表现。一类是运维脚本注册的第三方服务实例约 30 秒后从列表里消失,脚本反复重注册;另一类是把数据库代理登记成持久实例后,想删除时接口返回异常,且集群某节点不可写时这些实例也查不到。

排查:ephemeral 决定一致性协议、健康检查方式和生命周期,不是“一个标记”。

维度临时实例(ephemeral=true,默认)持久实例(ephemeral=false)
一致性Distro,AP,每节点可写、异步复制Raft,CP,需多数派
健康判定客户端心跳;15s 不健康、30s 剔除服务端主动探测
生命周期心跳停 → 自动剔除不自动消失,需显式删除
可用性代价少数派分区时仍可写,可能读到旧列表少数派不可写,注册/删除会失败
典型用途普通无状态微服务不能丢的静态服务、数据库代理

本例第一类是脚本只调一次注册、不发心跳;第二类是把实例登记成持久后,又在集群只有一个可用节点时尝试删除——CP 路径下多数派不足,操作失败属于预期行为。

对症做法:

bash
# 1. 脚本代注册要么持续发心跳,要么改成持久实例并接受 CP 代价
# 心跳方式(临时实例必须由“某处”持续维持,脚本要常驻)
while true; do
  curl -s -X PUT "http://127.0.0.1:8848/nacos/v3/client/ns/instance/beat" \
    -H "accessToken: $ACCESS_TOKEN" \
    -d "serviceName=legacy-api" -d "ip=10.0.3.21" -d "port=8080" \
    -d "namespaceId=public" -d "ephemeral=true" >/dev/null
  sleep 5
done
text
2. 常驻心跳脚本本身是可用性风险:它一挂,实例就消失。
   更稳的做法是给被登记方接入 SDK,或把它登记为持久实例并由变更流程管理生命周期。
3. 持久实例要接受三件事:删除需显式操作、少数派不可写时注册/查询失败、
   需要 7848 的 JRaft 端口连通;登记前先确认集群是 3 节点且健康。
4. 混用没问题,但要在文档里标清每个服务用的是哪一类,
   并把“为什么”写下来——临时实例读到旧列表是可接受代价,反之则要用 CP。

复盘要点:判断依据是“谁负责维持这个实例的存在”。有进程 → 临时实例 + SDK 心跳;没有进程能力但又不能丢 → 持久实例 + 变更流程;用脚本冒充进程心跳属于第三种,它把可用性绑在脚本上,最容易出事。相关剔除窗口与消费端缓存见 服务注册与发现,现场处置见 Nacos 生产踩坑。

最小上手 ​

起一个 3.2.4 单机实例。3.x 启动时必须给令牌和节点身份,否则直接启动失败:

bash
TOKEN=$(openssl rand -base64 32)
docker run -d --name nacos \
  -p 8080:8080 -p 8848:8848 -p 9848:9848 -p 9849:9849 \
  -e MODE=standalone \
  -e NACOS_AUTH_ENABLE=true \
  -e NACOS_AUTH_TOKEN="$TOKEN" \
  -e NACOS_AUTH_IDENTITY_KEY=serverIdentity \
  -e NACOS_AUTH_IDENTITY_VALUE=security \
  nacos/nacos-server:v3.2.4

浏览器打开 http://127.0.0.1:8080/,首次访问会要求初始化 nacos 管理员密码。然后登录拿 token,并用 v3 客户端 API 注册一个实例:

bash
ACCESS_TOKEN=$(curl -s -X POST "http://127.0.0.1:8848/nacos/v3/auth/user/login" \
  -d "username=nacos" -d "password=<初始化密码>" | sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p')

curl -s -X POST "http://127.0.0.1:8848/nacos/v3/client/ns/instance" \
  -H "accessToken: $ACCESS_TOKEN" \
  -d "serviceName=order-service" -d "ip=127.0.0.1" -d "port=8080" \
  -d "namespaceId=public" -d "ephemeral=true"

curl -s "http://127.0.0.1:8848/nacos/v3/client/ns/instance/list?serviceName=order-service&namespaceId=public" \
  -H "accessToken: $ACCESS_TOKEN"

ephemeral=true 的实例要靠心跳续命;只用 curl 注册、不发心跳,约 30s 后会被剔除。看到实例消失不是注册失败,而是生命周期到了。3.x 的 v1/v2 API 已剥离到 api-legacy-adapter,旧脚本不要再直接调 /nacos/v1/...。

生产动作 ​

  • 版本先定线:3.2.4 是当前稳定版;不能上 Java 17 就留在 2.5.4 并跟安全补丁,不要停在 2.4.x。
  • 端口一次放全:8848/9848/9849 必须通,集群再加 7848;控制台 8080 只对公司网开放。
  • 鉴权默认收紧:3.x 的控制台、Admin、Inner API 默认开启鉴权;NACOS_AUTH_TOKEN、NACOS_AUTH_IDENTITY_KEY/VALUE 所有节点必须一致。
  • 命名空间按环境切:dev/test/prod 用不同 Namespace ID;不要用同一个 public 承载所有环境。
  • 升级先读手册:2.x → 3.x 涉及 JDK 17、控制台端口、默认 Namespace、v1/v2 API、JRaft 鉴权,逐节点滚动并先备份 MySQL。
  • 监控四个面:gRPC 连接数、实例数、配置变更与推送失败、JVM/MySQL。连接数突增通常先于注册失败出现。

边界 ​

  • 不是配置数据库:Nacos 适合低频人工变更的配置,不适合每秒写入的业务 KV。
  • 不是服务网格:它给的是名单和配置,不代理数据面流量;熔断、重试、限流仍要在客户端或网关做。
  • 没有原生多活:跨机房要么客户端分别连本地集群,要么用 Nacos-Sync 一类外部同步,延迟与冲突要自己承担。
  • 3.x 不是纯升级:控制台端口、鉴权默认值、API 版本、默认 Namespace 都变了,按「小版本升级」的心态操作会踩坑。
  • 注册成功不等于可调用:消费方还有本地缓存 TTL,实例刚注册或刚下线都有一个可见性窗口。

讲清楚 ​

Nacos 把注册中心和配置中心放在一个进程里,但它们是两条独立链路:命名服务维护服务名到实例列表,临时实例走 Distro 的 AP 复制,持久实例走 Raft 的 CP;配置服务的真相在 MySQL,各节点只保留本地快照,变更通过通知加客户端拉取生效。2.x 起两者都走 gRPC 长连接,所以端口从 8848 扩到 9848/9849,只放通 8848 是最常见的「网络没问题却用不了」。选型时不要问谁最强,先看约束:要注册配置一体、团队小,Nacos 运维面最少;要跨机房原生复制,Consul 更合适;已有 ZK 生态就不要为统一而迁移。它的边界是:只解决「找谁」和「配置是什么」,不解决调用可靠性与多活。

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