nacos原理之服务注册浅析

前言

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层,namingServiceNacosNamingService的实例,它是Nacos客户端注册发现的核心实现类,调用registerInstance执行注册逻辑。

NamingUtils.checkInstanceIsLegal(instance);

checkAndStripGroupNamePrefix(instance, groupName);

方法进行参数检查后调用clientProxy.registerService方法。clientProxyNamingClientProxyDelegate类型,它在初始化时会根据实例类型选择不同的通信协议,如下:



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中,此时数据已经存在于内存中,可以被后续查询使用。但要让整个集群感知到这个变化,还需要:

  1. 建立服务与客户端的索引(便于按服务查询所有实例)
  2. 通知订阅了该服务的消费者(服务变更推送)
  3. 将数据同步到集群其他节点(Distro协议保证最终一致性)

这些后续操作都是通过发布事件触发的。

至此,客户端成功注册到服务端,关于其他知识点后续不定时时间更新

为什么 Nacos 2.x 选择 gRPC?

在 Nacos 1.x 中,临时实例通过 HTTP 定期发送心跳,存在以下问题:

  • 每个心跳都是短连接,频繁创建/销毁连接,开销大。
  • 服务端无法主动推送变更,只能靠客户端频繁拉取(轮询),延迟高且浪费资源。

gRPC 的引入解决了这些问题:

需求 传统 HTTP 方案 gRPC 方案
心跳保活 每 5 秒发送一次 HTTP 请求 长连接建立后,通过连接本身保活(Ping/Pong),无额外 HTTP 开销
服务变更推送 客户端每隔几秒拉取一次 服务端通过 gRPC 流主动推送,实时性高,当服务实例列表变化时,服务端通过已有的 gRPC 连接主动向订阅该服务的客户端发送更新(服务端流式 RPC)。客户端收到后刷新本地缓存,实现准实时发现。
资源占用 频繁连接/断开,消耗 CPU/端口 连接复用,多路复用,单连接可处理大量并发请求

一句话总结:gRPC 让临时实例的注册、心跳、服务发现变更推送都复用同一个长连接,大幅提升性能和实时性。

相关推荐
带娃的IT创业者2 小时前
解密OpenClaw系列11-OpenClaw自动更新系统
开发语言·软件工程·自动更新·软件发布·ai智能体·openclaw·桌面智能体
没有bug.的程序员2 小时前
Arthas 深度进阶:线上问题非侵入式诊断内核、方法级监控与线程阻塞排查实战指南
java·arthas·线上问题·非侵入式·方法级监控·线程阻塞
亓才孓2 小时前
[Mybatis]Mybatis框架
java·数据库·mybatis
跟Tom学编程—一对一编程辅导2 小时前
基于 Java 的 SSM 架构电子商城项目毕业设计课题选型指导文档|名企高级开发工程师全程一对一指导(含详细文档+源码+部署)
java·架构·毕业设计·课程设计
编程小风筝2 小时前
编写java代码如何写文档注释?
java·开发语言
lly2024062 小时前
HTML 媒体(Media)
开发语言
一个处女座的程序猿O(∩_∩)O3 小时前
Python函数参数*args和**kwargs完全指南:从入门到精通
开发语言·python
与衫3 小时前
如何将SQLFlow工具产生的血缘导入到Datahub平台中
java·开发语言·数据库
m0_531237173 小时前
C语言-分支与循环语句练习
c语言·开发语言