Nacos 如何实现服务注册?
Nacos 的服务注册实现机制是其作为注册中心的核心功能。简单来说,它是一个 “客户端发起注册 -> 服务端异步处理 -> 集群数据同步 -> 推送给消费者” 的过程。
为了深入理解,我们需要从 客户端(Client) 和 服务端(Server) 两个视角,结合 Nacos 1.x (HTTP) 和 Nacos 2.x (gRPC) 的架构差异来分析。
以下是详细的实现原理:
一、 核心模型:服务的分级存储
在了解流程前,必须知道 Nacos 服务端是如何存储数据的。Nacos 采用了一个多级 Map 的结构(ConcurrentHashMap)来存储服务信息:
Namespace (命名空间) -> Group (分组) -> Service (服务) -> Cluster (集群) -> Instance (实例列表)
二、 客户端 (Client) 的行为
当一个微服务启动时(例如 Spring Cloud 应用),Nacos Client 会执行以下操作:
发起注册 (Register):
- 客户端启动时,调用
NacosNamingService.registerInstance()方法。 - 它将自身的 IP、端口、服务名、元数据(Metadata)等信息封装成请求。
- Nacos 1.x: 发送 HTTP POST 请求 (
/nacos/v1/ns/instance)。 - Nacos 2.x: 建立 gRPC 长连接,发送注册请求。
- 客户端启动时,调用
心跳机制 (Heartbeat) - 仅针对临时实例:
- Nacos 的服务实例分为 临时实例 (Ephemeral) 和 持久实例 (Persistent)。Spring Cloud 默认是临时实例。
- 临时实例 (AP模式): 客户端维护一个定时任务,每隔 5秒 向服务端发送一次心跳。如果服务端超过 15秒 没收到心跳,将实例标记为不健康;超过 30秒,直接剔除该实例。
- 持久实例 (CP模式): 注册后信息永久保留,除非显式注销。服务端会主动探测其健康状态。
三、 服务端 (Server) 的处理流程
服务端收到注册请求后,为了保证高并发下的吞吐量,采用了 异步处理 和 内存读写分离 的策略。
1. 接收请求与任务分发
- 服务端收到注册请求后,并不会立即去修改底层的 ConcurrentHashMap(防止锁竞争太激烈)。
- 它会将请求封装成一个 Task,放入一个阻塞队列(BlockingQueue)中,或者通过
DistroProtocol(针对临时实例)进行处理。
2. 异步更新注册表 (Copy-On-Write)
后台线程从队列中获取任务,执行更新操作。为了解决并发读写冲突(消费者正在拉取服务列表,而生产者正在注册),Nacos 使用了 Copy-On-Write (写时复制) 思想:
- 取出当前服务的实例列表。
- 复制一份该列表。
- 在副本上进行添加/更新实例的操作。
- 用更新后的副本替换旧的列表(这一步是原子操作)。
- 优势: 读操作不需要加锁,极大地提高了服务发现(读)的性能。
3. 集群一致性同步 (Distro vs Raft)
Nacos 根据实例类型支持两种一致性协议:
AP 模式 (Distro 协议) - 默认/临时实例:
- 这是 Nacos 区别于 ZooKeeper 的核心。
- 去中心化: 每个 Nacos 节点都是平等的。
- 数据分片: 也就是将服务通过 Hash 算法分片给不同的节点负责(以此节点为 Leader)。
- 异步复制: 当节点收到属于它负责的服务的注册请求后,先更新本地,然后异步同步给集群中其他节点。
- 最终一致性: 允许短时间内数据不一致,但保证高可用。
CP 模式 (Raft 协议) - 持久实例:
- 使用 Raft 协议选举 Leader,数据必须写入半数以上节点才算成功。保证强一致性,但性能较差,可用性在选举期间受损。
4. 服务变更通知 (Push)
当服务列表发生变化(注册成功后):
- Nacos 1.x: 服务端通过 UDP 协议推送变更事件给订阅了该服务的客户端(消费者)。如果 UDP 丢包,客户端还有定时轮询(Pull)作为兜底。
- Nacos 2.x: 利用 gRPC 的长连接流式推送,更加实时和可靠。
四、 Nacos 1.x 与 2.x 的关键区别
Nacos 2.0 进行了架构升级,主要解决了 1.x 中 HTTP 短连接和心跳造成的性能瓶颈。
| 特性 | Nacos 1.x | Nacos 2.x |
|---|---|---|
| 通信协议 | HTTP (Restful API) | gRPC (长连接) |
| 心跳维持 | 客户端每5秒发送 HTTP 请求 | 基于 gRPC 长连接的连接状态判断 (Connection-based) |
| 性能瓶颈 | TPS 约 1-2万,大量心跳请求消耗 CPU/网络 | TPS 提升约 10倍,减少了连接创建销毁开销 |
| 服务发现 | 轮询 + UDP 推送 | gRPC 服务端主动推送 |
在 2.x 中:
客户端不再需要每 5 秒发送心跳包。只要 gRPC 长连接不断开,服务端就认为实例是健康的。如果连接断开,服务端会移除该连接上的所有实例。
五、 总结
Nacos 实现服务注册的核心逻辑可以概括为:
- 客户端启动并通过 HTTP/gRPC 发送注册信息。
- 服务端接收请求,将其放入异步队列。
- 后台线程利用 Copy-On-Write 机制更新内存中的注册表 (Map)。
- 如果是临时实例(默认),使用 Distro 协议异步同步给集群其他节点(AP模式,高性能)。
- 通过 UDP/gRPC 将变更推送给消费者客户端。
这种设计使得 Nacos 既能支撑海量微服务的注册(AP模式的高吞吐),又能保证服务发现的实时性。