前言
Nacos 2.x 选择 gRPC 让临时实例的注册、心跳、服务发现变更推送都复用同一个长连接,大幅提升性能和实时性。
介绍
nacos注册中心微服务默认都是以临时实例的形式注册的。Nacos中的临时实例是指仅存在于内存中、通过心跳维持存活、服务端主动剔除的实例类型,与之相对的是持久化实例,后者会被持久化到磁盘,需要服务端主动探活。临时实例的设计目标是为弹性伸缩场景服务------当流量高峰过去,实例下线后会自动从注册中心注销,无需人工干预。
本文基于Nacos3.1.1版本源码,完整梳理一个临时实例注册到服务端的过程。

客户端应用启动初始化注册的触发器
根据Spring Boot自动装配原理,spring-cloud-starter-alibaba-nacos-discovery包下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports定义了NacosServiceRegistryAutoConfiguration

NacosServiceRegistryAutoConfiguration中初始化了NacosAutoServiceRegistration,它是服务注册的触发器。

nacos服务注册服务类NacosServiceRegistry传入父类的构造方法中



客户端spring扩展事件触发注册逻辑
NacosAutoServiceRegistration 继承了AbstractAutoServiceRegistration类,AbstractAutoServiceRegistration类实现spring的ApplicationListener扩展接口,当web容器初始化完成触发start方法

在start方法调用了子类NacosAutoServiceRegistration的register()方法触发服务注册逻辑

子类中检查了是否开启注册开关和检查了端口,后调用父类register()方法

父类的register()方法中调用了NacosServiceRegistry(一开始初始化类传入)的register方法,触发注册逻辑。
(方法getRegistration()返回NacosRegistration作为入参,其中记录了客户端的基本信息,如端口、应用名等,当前不做讲解)

客户端NacosServiceRegistry执行注册逻辑
步骤一:选择通信协议
这里,我们已经从Spring配置层进入了Nacos Client SDK层,namingService是NacosNamingService的实例,它是Nacos客户端注册发现的核心实现类,调用registerInstance执行注册逻辑。

NamingUtils.checkInstanceIsLegal(instance);
checkAndStripGroupNamePrefix(instance, groupName);
方法进行参数检查后调用clientProxy.registerService方法。clientProxy是NamingClientProxyDelegate类型,它在初始化时会根据实例类型选择不同的通信协议,如下:



instance.isEphemeral()默认为true,表示临时实例,临时实例对延迟和吞吐量要求更高,使用gRPC基于HTTP/2,支持双向流、多路复用,比HTTP/1.1更适合心跳保活和频繁的注册/注销场景,Nacos 2.x版本开始,临时实例默认使用gRPC协议。


步骤二:调用服务端注册

这里的rpcClient是Nacos封装的gRPC客户端,它会与服务端建立长连接,并通过该连接发送注册请求。该连接被用于注册、心跳、订阅操作。

服务端
步骤一:gRPC 入口:InstanceRequestHandler
在客户端通过 NamingGrpcClientProxy 发送请求时,服务端对应的方法就是 InstanceRequestHandler.handle()

Nacos 2.x 临时实例的注册、心跳复用 gRPC 连接,将实例信息存入内存,并关联到该连接(Client 对象),不需要额外的 HTTP 心跳请求。只有持久化实例模式或旧版客户端才会走到register 接口(com.alibaba.nacos.naming.controllers.v3.InstanceControllerV3#register 接口。
步骤二:服务注册核心逻辑
临时实例的注册由EphemeralClientOperationServiceImpl完成

在Nacos服务端源码中,将服务实例信息添加到Client对象后,表面上看起来没有对client做更多操作,但实际上,后续的流程是通过事件机制 和异步任务 来完成的。这种设计使得核心注册逻辑简洁,同时将数据同步、索引更新、服务推送等职责解耦到其他组件中。


client.addServiceInstance(singleton, instanceInfo); 将实例信息放入了AbstractClient#publishers这个Map中,此时数据已经存在于内存中,可以被后续查询使用。但要让整个集群感知到这个变化,还需要:
- 建立服务与客户端的索引(便于按服务查询所有实例)
- 通知订阅了该服务的消费者(服务变更推送)
- 将数据同步到集群其他节点(Distro协议保证最终一致性)
这些后续操作都是通过发布事件触发的。
至此,客户端成功注册到服务端,关于其他知识点后续不定时时间更新
为什么 Nacos 2.x 选择 gRPC?
在 Nacos 1.x 中,临时实例通过 HTTP 定期发送心跳,存在以下问题:
- 每个心跳都是短连接,频繁创建/销毁连接,开销大。
- 服务端无法主动推送变更,只能靠客户端频繁拉取(轮询),延迟高且浪费资源。
gRPC 的引入解决了这些问题:
| 需求 | 传统 HTTP 方案 | gRPC 方案 |
|---|---|---|
| 心跳保活 | 每 5 秒发送一次 HTTP 请求 | 长连接建立后,通过连接本身保活(Ping/Pong),无额外 HTTP 开销 |
| 服务变更推送 | 客户端每隔几秒拉取一次 | 服务端通过 gRPC 流主动推送,实时性高,当服务实例列表变化时,服务端通过已有的 gRPC 连接主动向订阅该服务的客户端发送更新(服务端流式 RPC)。客户端收到后刷新本地缓存,实现准实时发现。 |
| 资源占用 | 频繁连接/断开,消耗 CPU/端口 | 连接复用,多路复用,单连接可处理大量并发请求 |
一句话总结:gRPC 让临时实例的注册、心跳、服务发现变更推送都复用同一个长连接,大幅提升性能和实时性。