当前位置: 首页 > news >正文

服务端收到一个注册请求,从 Controller 到内存 Map 走了 5 层:服务注册模块源码走读

服务端收到一个注册请求,从 Controller 到内存 Map 走了 5 层:服务注册模块源码走读


文章目录

  • 服务端收到一个注册请求,从 Controller 到内存 Map 走了 5 层:服务注册模块源码走读
    • 客户端发出注册请求之后,服务端做了什么
    • 请求入口:gRPC Handler 还是 HTTP Controller
    • 五层链路:从请求到内存
      • 第一层:Handler 解析请求
      • 第二层:ClientOperationService 处理注册
      • 第三层:ClientManager 管理连接
      • 第四层:ConsistencyService 决定协议
      • 第五层:异步通知订阅者
    • 数据存哪了:内存注册表结构
    • 一次注册的完整事件链
    • 一张图带走:服务注册模块全链路

客户端发出注册请求之后,服务端做了什么

上一篇讲客户端 SDK 时,我们看到registerInstance最终通过 gRPC 把一个InstanceRequest发给了服务端。

那服务端收到这个请求之后呢?它要把这个实例存到哪里?存的时候走 Distro 还是 Raft?其他节点怎么知道多了一个实例?

这篇文章站在服务端视角,从请求入口开始,拆开一次注册请求在 Nacos-Server 内部的完整链路:Controller 收请求 → ServiceManager 管理服务 → ConsistencyService 决定一致性协议 → 数据落内存 → 异步通知订阅者。每一步给出类名和方法,你可以对着 nacos-naming 模块的源码跟。

⚠️ 本文基于 Nacos 2.3.0 源码。2.x 的注册入口是 gRPC 的InstanceRequestHandler,1.x 是 HTTP 的InstanceController。本文以 2.x gRPC 链路为主,同时标注 1.x 的对应类。


请求入口:gRPC Handler 还是 HTTP Controller

Nacos 2.x 客户端默认走 gRPC,所以注册请求先落到 gRPC 的请求处理器:

// 2.x gRPC 入口@ComponentpublicclassInstanceRequestHandlerextendsRequestHandler<InstanceRequest,InstanceResponse>{@OverridepublicInstanceResponsehandle(InstanceRequestrequest,RequestMetameta){// 根据请求类型分发:注册 / 注销switch(request.getType()){caseNamingRemoteConstants.REGISTER_INSTANCE:returnregisterInstance(...);caseNamingRemoteConstants.DE_REGISTER_INSTANCE:returnderegisterInstance(...);}}}

对比一下两个版本的入口,方便你在源码里定位:

版本通信方式入口类核心方法
2.xgRPCInstanceRequestHandlerhandle(InstanceRequest, RequestMeta)
1.xHTTPInstanceControllerregister(HttpServletRequest)

两条路最终都汇聚到同一个地方——ServiceManager或 2.x 的EphemeralClientOperationServiceImpl入口不同,核心处理逻辑复用。


五层链路:从请求到内存

把一次注册拆开,服务端内部走了 5 层:

订阅者通知ConsistencyServiceClientManagerClientOperationServiceInstanceRequestHandler客户端 gRPC订阅者通知ConsistencyServiceClientManagerClientOperationServiceInstanceRequestHandler客户端 gRPCInstanceRequest(REGISTER)registerInstance(service, instance, clientId)更新 Client 持有的实例触发 ClientRegisterServiceEventDistro 协议同步到其他节点发布 ServiceChangedEvent推送给订阅该服务的客户端

第一层:Handler 解析请求

InstanceRequestHandler把 gRPC 的InstanceRequest拆出ServiceInstanceclientId三个关键对象。clientId是 2.x 的重要变化——每个客户端连接对应一个唯一 clientId,服务端以 client 为维度管理它注册的所有实例。

第二层:ClientOperationService 处理注册

// EphemeralClientOperationServiceImpl(临时实例)@OverridepublicvoidregisterInstance(Serviceservice,Instanceinstance,StringclientId){// 1. 拿到该 clientId 对应的 Client 对象Clientclient=clientManager.getClient(clientId);// 2. 把实例塞进 Client 的持有列表InstancePublishInfoinstanceInfo=getPublishInfo(instance);client.addServiceInstance(service,instanceInfo);// 3. 发布注册事件,触发后续同步和通知NotifyCenter.publishEvent(newClientRegisterServiceEvent(service,clientId));}

这里体现了 2.x 的核心设计变化:数据以 Client 为中心组织,而不是 1.x 的以 Service 为中心。一个客户端断连,它注册的所有实例一次性清理,效率更高。

第三层:ClientManager 管理连接

ClientManager维护clientId → Client的映射。Client 分两类:

Client 类型场景生命周期
ConnectionBasedClientgRPC 长连接注册的临时实例连接断开即清理
IpPortBasedClientHTTP 注册的临时实例靠心跳保活
持久化实例通过 Raft 存储显式注销才删除

第四层:ConsistencyService 决定协议

这是最关键的分叉点。实例是临时的还是持久化的,决定了走哪个一致性协议:

true 临时实例

false 持久化实例

注册请求

ephemeral?

Distro 协议 (AP)
本节点先写,异步同步其他节点

JRaft 协议 (CP)
Leader 提交日志,多数派确认

写入内存注册表

临时实例走 AP,追求可用性——本节点写完立即返回,异步同步。持久化实例走 CP,追求一致性——必须多数派确认才算成功。

第五层:异步通知订阅者

注册成功后,NotifyCenter发布ServiceChangedEvent。订阅了这个服务的客户端会收到 gRPC 推送,拿到最新的实例列表。这一步是异步的,不阻塞注册请求的返回。


数据存哪了:内存注册表结构

很多人以为实例注册后存进了 MySQL。错。临时实例只存在内存里,MySQL 不参与临时实例的存储。

2.x 的内存注册表核心是这几个数据结构:

// ClientManager 中的核心 Map// key = clientId, value = Client 对象privatefinalConcurrentMap<String,Client>clients;// Client 内部持有的实例// key = Service, value = InstancePublishInfoprivatefinalConcurrentHashMap<Service,InstancePublishInfo>publishers;

当客户端查询某个服务的实例列表时,ServiceStorage会遍历所有 Client,聚合出该服务下的全部实例:

查询 order-service 实例

ClientServiceIndexesManager
反向索引: Service → clientId 集合

遍历这些 Client
聚合实例

返回实例列表

ClientServiceIndexesManager维护了Service → 注册了该服务的 clientId 集合的反向索引。查询时不用扫全部 Client,直接通过索引定位,这是 2.x 性能优化的关键之一。


一次注册的完整事件链

Nacos 2.x 用事件驱动串起整个注册流程。理解这条事件链,就理解了注册模块:

事件发布者订阅者作用
ClientRegisterServiceEventClientOperationService索引管理器更新反向索引
ClientChangedEventClientManagerDistro 同步器触发跨节点同步
ServiceChangedEvent索引管理器推送引擎通知订阅者
MetadataEvent元数据管理器元数据存储更新服务元信息

事件之间解耦,每个订阅者只关心自己的职责。这也是为什么注册请求能快速返回——重活都甩给异步事件处理了。


一张图带走:服务注册模块全链路

通知

一致性

核心处理

请求入口

gRPC: InstanceRequestHandler

HTTP: InstanceController(1.x)

ClientOperationService
registerInstance

ClientManager
clientId → Client

ClientServiceIndexesManager
Service → clientId 反向索引

Distro 协议
临时实例 AP

JRaft 协议
持久化实例 CP

NotifyCenter
ServiceChangedEvent

推送引擎
gRPC 通知订阅者

入口(gRPC/HTTP) → ClientOperationService → ClientManager 存内存 + 反向索引 → 一致性协议同步 → NotifyCenter 异步通知订阅者。临时实例全程不落 MySQL。


你们线上服务"注册上了但发现不到",最后排查出的根因是什么?评论区留数字:1=namespace 不匹配 2=group 配错 3=集群节点数据未同步 4=客户端缓存没刷新 5=健康检查把实例剔了

http://www.jsqmd.com/news/1162632/

相关文章:

  • CodeContracts架构解析:.NET契约式设计的深度优化与实战指南
  • STM32与TLP241A实现工业级电气隔离设计
  • SH9递归对抗动力学RAD:一种基于自指与矛盾驱动的认知系统演化理论(世毫九实验室原创理论)
  • STM32驱动压电蜂鸣器实现高可靠性警报系统
  • 终极指南:如何用Cypht一站式管理所有邮箱账号
  • 帝舵中国官方售后服务中心|服务电话及详细网点地址权威信息公示(2026年7月最新) - 帝舵中国官方服务中心
  • 基于压电换能器与MCU的高效声音警报系统设计
  • 揭秘成都回收行业内幕,合扬包包回收直接公开全部计价方式 - 开心测评
  • Reflex Platform缓存机制详解:加速Haskell项目构建的终极技巧
  • ADP5350与PIC18F47K40的嵌入式电源管理方案
  • 17.高速DAC输出电路设计
  • 亲身探访南京萧官方售后服务中心|全新服务电话及详细维修地址(2026年7月最新) - 萧邦中国官方服务中心
  • 终极指南:Godot根运动动画实现专业级角色控制系统
  • 工业级警报系统:压电蜂鸣器与MCU的硬件设计与软件实现
  • 郑州金水区收钻石,不收鉴定费、折旧费、损耗费 - 全城热点
  • TMSpeech:Windows本地实时语音转文字终极指南,CPU占用不到5%
  • 感知流匹配(PFM):1-4步实现高质量图像生成的突破性技术
  • 22位ADC与PIC单片机的高精度数据采集系统设计
  • AI驱动的合约升级风险评估:存储布局兼容性的自动化分析
  • ECC:让AI真正理解你的开发工作流
  • 3个简单步骤:将闲置电视盒变身高性能Linux服务器
  • Navicat试用期重置:让数据库管理工具持续为你服务
  • 3分钟免费解锁WeMod专业版:终极破解工具Wand-Enhancer完整指南
  • 3PEAK思瑞浦 TP1962-FR DFN2X2-8 比较器
  • Neko漫画阅读器终极指南:如何轻松整合Toonily与Weeb Central等多平台资源
  • 2026年7月最新西安积家官方售后热线及客户服务网点地址 - 积家官方售后服务中心
  • 零代码AI自动化神器:Midscene.js让你的跨平台UI测试变得如此简单
  • DeepTutor AI模型集成终极指南:构建智能学习助手的5大技术策略
  • Support App性能优化技巧:提升应用响应速度的7个方法
  • 2026河南CPPM线上课程选择指南:正规性与内容核验要点 - 企智芯