Nacos 服务注册与发现原理
服务注册与发现是 Nacos 的核心能力。本文拆解完整链路:注册请求如何被处理、心跳如何续约、健康状态如何判定、实例变化如何触达客户端。
注册链路总览
客户端(gRPC)
├─ 1. InstanceRequest(注册)
│ │
│ ▼
│ Nacos Server: InstanceController → ServiceOperator
│ │ 1. 解析 Namespace/Group/Service
│ │ 2. 组装 Instance 写入注册表(ServiceManager)
│ │ 3. Distro 异步同步到其他节点
│ │ 4. 发布 ServiceChangedEvent(触发推送)
│ ▼
│ 注册完成
│
└─ 2. 心跳续约(BeatRequest,默认 5s 一次)
│
▼
更新实例 lastBeatTime(续约成功)关键组件
| 组件 | 职责 |
|---|---|
| ServiceManager | 注册表核心:维护 Service 与 Instance 的内存模型 |
| InstanceOperator | 实例操作入口(校验、注册、删除) |
| ConsistencyService | 一致性写入入口(AP 走 Distro,CP 走 JRaft) |
| DistroTaskEngine | AP 数据同步任务引擎 |
服务注册请求处理
注册数据模型
java
// 客户端发来的实例数据
public class Instance {
private String serviceName; // 服务名
private String ip; // IP
private int port; // 端口
private String clusterName; // 集群名(可用区)
private boolean healthy; // 健康状态
private boolean ephemeral; // 临时/持久
private Map<String, String> metadata; // 元数据(权重、版本等)
}服务端处理流程
1. InstanceOperatorClientImpl.registerInstance(instance)
├─ 参数校验(ip/port/serviceName 非空)
├─ 计算 instanceId(md5)
├─ 找到或创建 Service(ServiceManager)
├─ 加入服务集群的实例列表
└─ 发布 InstanceEvent
2. 数据一致性写入
├─ 临时实例 → ConsistencyService(Distro)→ 内存 + 异步同步
└─ 持久实例 → JRaft → 写入 MySQL
3. 发布变更事件
└─ ServiceChangedEvent → 触发对订阅者的推送心跳续约机制
客户端心跳
- 默认 5 秒一次(
nacos.naming.heart.beat.interval) - 通过 gRPC 长连接发送 BeatRequest
- 服务端收到后更新实例的
lastBeatTime
健康检查:三种机制
| 机制 | 对象 | 原理 |
|---|---|---|
| 客户端心跳 | 临时实例 | 超过 heartbeatTimeout(默认 15s)未收到心跳 → 标记不健康 |
| 服务端主动探测 | 持久实例 | 服务端定时发送 TCP/HTTP 探测,失败则标记不健康 |
| 健康检查任务 | 全部 | 服务端定时扫描实例列表,清除过期实例 |
实例状态流转
注册成功(healthy=true)
│
├─ 心跳正常 → 保持 healthy
├─ 超过 15s 无心跳 → healthy=false(仍保留在列表)
└─ 超过 30s 无心跳 → 从注册表删除(remove)删除后立即触发推送,让消费者不再路由到该实例。
发现机制:Push vs 拉取
方式一:Push(服务端推送)
Nacos 2.x 基于 gRPC 长连接的主动推送:
服务端实例变化(注册/注销/健康变更)
│
├─ ServiceChangedEvent
├─ PushService:找到所有订阅该服务的连接
└─ 通过 gRPC 流推送最新实例列表
│
▼
客户端更新本地缓存 + 触发监听器方式二:轮询拉取(客户端兜底)
客户端定时(默认 10s)或重连后
└─ 发送 SubscribeServiceRequest
│
▼
Nacos 返回最新实例列表(服务端可能带版本号)客户端同时维持:
- 长连接实时接收 Push
- 定时/重连兜底拉取(防止 Push 丢失)
1.x 与 2.x 推送对比
| 版本 | 推送方式 | 可靠性 |
|---|---|---|
| 1.x | UDP 推送(不可靠) | 客户端 10s 轮询兜底,变更感知最长延迟约 10s |
| 2.x | gRPC 双向流推送 | 可靠长连接,变更秒级感知,性能更好 |
订阅通知的完整时序
消费者启动
├─ 1. 向 Nacos 订阅 service(SubscribeService)
│
├─ 2. 立即获得当前实例列表(首次拉取)
│
├─ 3. 常驻 gRPC 连接等待推送
│
├─ 4. 提供者注册新实例
│ └─ 服务端广播 → 消费者收到新列表
│
└─ 5. 消费者本地缓存更新,负载均衡用新列表高可用与容错
| 场景 | 处理 |
|---|---|
| 推送失败 | 客户端定时拉取兜底 |
| 服务端节点故障 | 客户端自动重连其他节点(域名解析到集群) |
| 客户端本地缓存 | 保留最近一次实例列表,Nacos 全挂时仍可用旧列表 |
| 注册风暴 | 服务端对频繁变更做合并推送 |
常见问题
- 注册后多久能被发现? Push 秒级;若只依赖轮询最长约 10s;2.x 默认 Push 实时性远好于 1.x。
- 实例健康检查多久判定失败? 临时实例默认 15s 无心跳标记不健康,30s 未恢复移除。
- 为什么设置了健康检查实例还是被摘除? 检查客户端心跳线程是否存活、时钟是否偏移、网络是否抖动。
- 临时实例挂了会自动恢复吗? 心跳恢复后自动回 healthy,无需人工干预。