主题
定位与整体架构
一句话: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.x | UDP 推送 + 客户端定时拉取兜底 | HTTP 长轮询,服务端挂起约 30s | UDP 不可靠;长轮询占用连接与线程 |
| 2.x | gRPC 双向流长连接 | 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 只是入口
| 端口 | 用途 | 谁连它 |
|---|---|---|
| 8848 | HTTP OpenAPI / AdminAPI | 运维脚本、SDK 的 HTTP 兜底;3.x 控制台不再走这里 |
| 9848 | 客户端 gRPC | 所有 2.x/3.x SDK 客户端 |
| 9849 | 集群间 gRPC | Nacos 节点之间 |
| 7848 | JRaft | 持久实例的 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.x | Eureka 1.x | Consul | ZooKeeper | Apollo |
|---|---|---|---|---|---|
| 一致性 | 临时实例 AP、持久实例 CP | AP + 自我保护 | CP(Raft) | CP(ZAB) | 配置走 DB,非共识协议 |
| 健康检查 | 客户端心跳 + 服务端主动探测 | 客户端心跳 | Agent 的 TCP/HTTP/TTL 检查 | Session | 不涉及实例 |
| 配置推送 | gRPC 长连接 | 无配置中心 | Blocking Query/Watch | Watch | HTTP 长轮询(默认 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 不一致时表现为间歇失败 |
| 默认 Namespace | public 用空字符串表示 | 默认 public 为字面量 | 老客户端传空串,新服务端按 public 查,读到空 |
| 运行时 | JDK 8/11 可用 | 需要 Java 17 | 启动即失败或 systemd 单元里 JDK 版本不符 |
| 持久实例共识 | 自研 CP | JRaft(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=securitytext
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 失败计数 | 队列积压 → 心跳与订阅响应被延后 |
| JVM | GC 停顿、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
donetext
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 生态就不要为统一而迁移。它的边界是:只解决「找谁」和「配置是什么」,不解决调用可靠性与多活。