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 让临时实例的注册、心跳、服务发现变更推送都复用同一个长连接,大幅提升性能和实时性。

相关推荐
愿天堂没有C++3 小时前
Pimpl 设计模式(指针指向实现)
开发语言·c++·设计模式
吾日三省Java3 小时前
GracefulResponse:告别手动Result包装,拥抱企业级统一响应处理
java·微服务·系统架构
Nuopiane3 小时前
MyPal3(4)
java·开发语言
lang201509283 小时前
24 Byte Buddy 进阶指南:5 种“特种”实现策略,让字节码操作更优雅
java·byte buddy
rannn_1113 小时前
【Redis|实战篇1】黑马点评|短信登录功能实现
java·redis·后端·缓存·项目
深耕AI3 小时前
【 从零开始的VS Code Python环境配置:uv】
开发语言·python·uv
AI_56783 小时前
RabbitMQ消息队列:高可用集群搭建与消息幂等处理
开发语言·后端·ruby
古城小栈3 小时前
Rust 1.94.0 闪亮登台
开发语言·后端·rust
弹简特3 小时前
【JavaEE15-后端部分】SpringBoot配置文件的介绍
java·spring boot·后端
SEO-狼术3 小时前
Convert HTML Tables to PDF in Python
开发语言·python·pdf