Nacos 如何实现服务注册与发现的?
结论先行:Nacos 分为服务端(命名服务 + 配置服务)与客户端。注册时客户端把 namespace/group/service/cluster/ip:port/weight/metadata 上报,临时实例写入内存并靠心跳保活,持久实例写入存储并走 Raft;发现时客户端定时拉取 + 服务端推送双通道,并在本地保留快照,注册中心不可用时降级读快照。
1. 整体结构
┌──────────── Nacos Server 集群 ────────────┐
│ Naming 模块 Config 模块 │
│ ├ 临时实例:内存 + Distro 同步(AP) │
│ └ 持久实例:JRaft + 存储(CP) │
└───────────────▲───────────────────────────┘
注册/心跳/查询(1.x HTTP,2.x gRPC 长连接)
│
┌────────────────────────────────┴──────────────────────────────┐
│ Provider:启动注册,每 5s 心跳 │
│ Consumer:订阅服务,本地缓存实例列表,收到变更推送后刷新 │
└───────────────────────────────────────────────────────────────┘
2. 注册流程
| 步骤 | 动作 | 说明 |
|---|---|---|
| 1 | 客户端构造实例信息 | ip、port、weight、cluster、metadata、ephemeral |
| 2 | 调用 registerInstance | 1.x 走 HTTP POST /nacos/v1/ns/instance,2.x 走 gRPC |
| 3 | 服务端校验并写入 | 临时实例进内存 Service 的实例表;持久实例落盘 |
| 4 | 集群同步 | 临时实例由 Distro 异步广播到其他节点;持久实例走 Raft 复制 |
| 5 | 返回注册结果 | 客户端记录本地状态,开始心跳 |
3. 心跳与健康检查
t=0s 注册成功
t=5s 心跳(默认每 5s 一次)
t=15s 服务端标记 unhealthy(超过 15s 未收到心跳)
t=30s 从实例列表剔除(超过 30s 未收到心跳)
| 参数 | 默认值 | 含义 |
|---|---|---|
heartbeatInterval | 5s | 客户端心跳间隔 |
heartbeatTimeout | 15s | 超过则标记不健康 |
ipDeleteTimeout | 30s | 超过则剔除实例 |
| 保护阈值 | 0 | 健康实例比例低于阈值时,仍返回不健康实例(防雪崩) |
持久实例不靠心跳,而由服务端主动探测(TCP/HTTP/MySQL 等),探测失败标记不健康。
4. 服务发现:拉 + 推
Consumer 启动
├─ ① 订阅:subscribe(serviceName, clusters)
├─ ② 立即拉取全量实例:getAllInstances
├─ ③ 写入本地缓存(内存 + 磁盘快照)
└─ ④ 等待推送:服务端实例变更 → UDP(1.x)/ gRPC 长连接(2.x)通知
└─ 收到通知后重新拉取增量/全量,更新本地缓存
| 机制 | 优点 | 缺点 |
|---|---|---|
| 定时拉取(1.x 默认 10s) | 实现简单、最终一致 | 变更延迟、注册中心压力大 |
| 服务端推送 | 变更秒级感知 | 需要维护长连接/UDP 通道 |
| 本地快照 | 注册中心宕机仍可调用 | 数据可能过期 |
两者结合:推送负责「快」,拉取负责「兜底对账」。所以 Nacos 客户端实际是「推送触发 + 拉取校正」。
5. 客户端配置
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: 8f3c1a2b-... # 用 ID,不要用名称
group: TRADE
cluster-name: SH
ephemeral: true
register-enabled: true
watch-delay: 30000 # 订阅推送延迟合并
踩坑点:namespace 必须填命名空间 ID,填名称会注册到 public;group 不一致会导致「注册成功但调用方发现不了」;cluster-name 影响同机房优先策略。
6. 服务端数据模型
namespace(环境隔离)
└─ group(业务分组,默认 DEFAULT_GROUP)
└─ service(服务名)
├─ cluster(机房/可用区)
│ └─ instance(ip:port, weight, healthy, ephemeral)
└─ cluster ...
调用方查询时可指定 group + clusters,Nacos 只返回匹配的实例,天然支持多机房就近调用。
7. 常见追问
追问一:Nacos 2.x 为什么改用 gRPC? 1.x 的 HTTP 短连接 + UDP 推送在实例规模大时开销高。2.x 用 gRPC 长连接统一注册、心跳、订阅推送,服务端能实时感知连接断开,剔除更及时(连接断即触发下线)。
追问二:临时实例和持久实例怎么选? 绝大多数微服务用临时实例(AP,心跳保活,宕机快速剔除)。需要「实例信息长期存在、不因客户端掉线而消失」的场景(如 DNS 风格的服务、数据库地址)才用持久实例。
追问三:注册中心全挂,调用链会断吗? 不会立即断。消费者有本地快照,可以继续按旧列表调用;但新实例上线感知不到,扩缩容失效。生产上 Nacos 必须集群部署(至少 3 节点)。
追问四:Distro 协议是什么? Nacos 自研的 AP 协议:每个节点负责一部分服务的写,收到写请求后异步广播给其他节点,读可以在任意节点完成。它保证最终一致,不保证强一致,换来的是高可用与低延迟。
追问五:如何做到「同机房优先」?
实例注册时带 cluster-name,消费端配置相同的 cluster-name,Nacos 返回实例时优先同集群;同集群全部不可用时才回退到其他集群。
小结
Nacos 的注册发现可以概括为:注册写内存/存储,心跳保活,Distro/Raft 做集群同步,消费端「推送 + 拉取 + 本地快照」三件套保证感知与容灾。理解这套机制,才能解释「为什么实例下线还有几十秒流量」「为什么注册中心挂了还能调用」这些线上现象。