第一篇:Nacos 2.x 架构全览——为什么从HTTP转向gRPC?

Nacos (Dynamic Naming and Configuration Service) 是一个旨在简化云原生应用构建的动态服务发现、配置管理与服务管理平台。

本次深入探索的 2.5.2 版本,是 Nacos 2.x 系列的收官之作,也是企业用于生产环境的首选版本,兼具极致的稳定性与代表性。相较于 Nacos 1.x来说,Nacos 2.x 最大的变革在于架构的根本性升级:通过引入长连接模型,不仅实现了通信效率的跃升,还彻底移除了对旧有依赖(如 MySQL)的强绑定,使系统更加轻量与高可用。

Nacos官方网站:nacos.io/

一、快速开始:Idea启动Nacos 2.5.2

在正式进入源码分析之前,第一步需要先将Nacos 2.5.2的源码从GitHub拉取到本地,导入IntelliJ IDEA并完成运行时环境的配置。具体步骤包括:从官方仓库克隆2.5.2版本的代码、在IDEA中以Maven项目形式导入、配置JDK版本与Maven参数、等待依赖下载与源码编译完成。编译通过后,找到console模块下的启动类,以单机模式运行。

当控制台打印出启动成功的日志,浏览器打开Nacos控制台页面并成功调用一次配置发布或服务注册接口时,你一定会获得成就感。保持这种感觉,跟随文章继续往下探索,相信你一定会有更多的收获。

进入Nacos的github地址:github.com/alibaba/nac... ,选择2.5.2版本的tag下载,解压后导入Idea

运行环境:

JDK17、Maven 3.9.14

cd yourPath/nacos-2.5.2/consistency mvn clean install -DskipTests=true // 编译gRPC文件

点击Sync All Maven Projects

cd yourPath/nacos-2.5.2 mvn clean install -DskipTests=true // 整体编译

找到 yourPath/nacos-2.5.2/console/src/main/java/com/alibaba/nacos/Nacos.java

Idea内置运行设置VM options参数-Dnacos.standalone=true //以单机模式启动

运行即可

二、Nacos源码的整体架构及设计思想:先学思想,再考虑实现

学习一门顶级开源项目,首要任务不是逐行阅读源码,而是先建立对整体架构的全局认知。如果缺乏架构地图就冒然深入细节,容易在复杂的调用链中迷失方向,效率低下且难以形成体系化的理解。因此在正式进入Nacos 2.5.2源码之前,有必要先将其整体架构骨架梳理清楚,各层职责、模块边界、调用关系,为后续深入各子系统提供一张清晰的地图。

整体架构:参考官网的架构图

Nacos在微服务体系中承担两项核心职能:配置中心注册中心 。从架构上,系统天然划分为两类角色:ClientServer 。应用项目引入nacos-client依赖后即成为Client端;独立部署的nacos-server进程则为Server端。Client负责发起请求,Server负责处理请求、管理数据、维护集群一致性。Nacos的整体源码架构,本质上就是围绕"Client如何与Server交互"这一命题逐层展开的。

作为服务端,Server必须对外暴露接口。这一层在源码中对应OpenAPI层,即接入层,是所有外部请求的统一入口。该层承载两套通信协议,各自服务于不同的调用场景。

HTTP协议主要面向两个方向。其一是Nacos控制台的前端交互,运维人员在浏览器中的操作------查看服务列表、编辑配置项、管理命名空间------均通过HTTP接口与Server通信。其二是对外开放的OpenAPI能力,供外部系统或运维脚本以RESTful方式调用,例如通过curl直接注册服务实例或发布配置。

gRPC协议则专门承载Client与Server之间的内部通信,这也是Nacos 2.x架构相较于1.x的核心演进点。应用项目通过Nacos Client发起的操作如查询配置、发布配置、注册监听器、服务注册与发现,最终均通过gRPC长连接发送至Server端。相较于HTTP短连接,gRPC基于HTTP/2的长连接机制在实时性、吞吐量及资源开销方面具备显著优势,是Nacos 2.x能够支撑更大规模集群的关键技术决策。

请求通过OpenAPI层进入系统后,由业务实现层 承接。Nacos 2.5.2在该层划分为两大核心模块:配置中心服务注册中心服务 ,分别对应config模块和naming模块,职责边界明确,各自独立演进。

配置中心服务负责配置的全生命周期管理,包括配置的增删改查、灰度发布、历史版本回滚、以及监听器注册与回调。注册中心服务则管理服务实例的注册与注销、健康检查、元数据维护,以及服务订阅者的变更推送。理解这两大模块的职责划分,是后续深入源码阅读的第一个关键里程碑。

业务实现层之下是核心层,该层不直接处理外部请求,而是为上层业务提供全局性的底层能力支撑。其中,一致性协议模块同时支持AP模式的Distro协议与CP模式的JRaft协议,分别服务于注册中心与配置中心的不同一致性需求------注册中心强调可用性,配置中心强调数据一致性。集群管理模块负责节点间通信、成员状态维护与故障检测。通知机制模块则确保配置变更或服务实例上下线事件能够准确实时地推送至所有相关Client。核心层决定了整个系统的稳定性上限,是Nacos的"发动机舱"。

最底层为持久层,负责数据的最终存储。配置数据及持久化的服务数据均需落盘或落库,该层封装了对底层存储的全部访问细节,向上层暴露统一接口,使业务层无需感知具体存储实现。Nacos 2.5.2支持嵌入式Derby与外部MySQL两种方案:单机模式默认使用Derby以降低外部依赖;生产集群模式则要求MySQL以保证高可用。Derby作为轻量级内嵌数据库,在开发测试与单机部署场景中也能够满足基本需求。

将四层串联起来,一条完整的请求链路是这样的:Client通过gRPC向Server发起配置查询请求,OpenAPI层的gRPC模块负责接收并解析请求体,随后将请求转发至配置中心服务的实现层。实现层处理业务逻辑,包括校验权限和判断灰度策略,当需要读取配置数据时,调用核心层的一致性模块确定应从哪个节点读取数据,最终由持久层从MySQL中查询出配置内容。返回数据沿原路回传,从持久层逐级向上,经由gRPC长连接推送回Client。整个链路自上而下再自下而上,每一层只做自己分内的事,层与层之间通过明确的接口通信,职责内聚、边界清晰,这就是Nacos源码架构贯彻的分层设计思想。

源码架构:以功能的维度分层分析

理解了整体架构之后,我们结合源码的目录结构继续分析Nacos在实现上是如何分层的。这里可以使用Idea结合Module的Diagram ---> Project Modules或是每个Module的pom文件进行分析。

从功能维度出发,Nacos 2.5.2 的源码可以清晰地划分为五个层次,从上到下依次为:管理及入口层、核心服务层、通信与客户端层、插件扩展层、公共基础设施层。每一层职责内聚,层与层之间通过明确的接口通信,共同构成了 Nacos 的整体功能版图。

管理及入口层 是系统的统一入口与交互界面。console 模块集成了所有子模块并提供了全局启动入口 Nacos.javaconsole-ui 提供前端控制台页面,address 模块负责集群地址发现与节点列表维护。这一层对外暴露的能力包括服务列表与详情管理、集群节点状态查看、配置编辑与历史回滚、命名空间管理、权限控制与用户管理,以及集群地址服务器发现。

核心服务层 承载了 Nacos 最核心的业务逻辑。该层由 core 核心抽象、naming 注册中心、config 配置中心三大模块组成,向下依赖一致性协议层的 Distro 协议和 Raft 协议------Distro 面向 AP 场景,用于服务数据的最终一致性同步;Raft 面向 CP 场景,用于配置数据的强一致性同步。再向下是数据持久化层,包含内嵌内存数据库 Derby 和外置数据库 MySQL 两种方案,分别适用于开发测试单机模式与生产集群模式。核心服务层整体依赖于 auth 鉴权认证层,提供登录认证、Token 管理及权限控制能力。

通信与客户端层 负责 Nacos 的网络通信抽象与客户端 SDK 实现。remote 模块(非独立模块,在core目录下)定义通信契约,包括请求与响应模型、双向流式通信及连接事件处理;grpc 模块(非独立模块,在remote目录下)基于此契约提供具体的 gRPC 实现,涵盖 gRPC Server 与 Client 的构建、双向流管理、连接心跳与重连机制,以及客户端侧的负载均衡策略;client 模块作为面向最终用户的 SDK,封装了底层连接逻辑,向上暴露服务发现 API 以及配置获取与监听接口。整条通信链路的设计原则是:remote 定义"怎么通信"的抽象契约,grpc 基于 gRPC 协议完成具体实现,client 则将通信能力封装为开箱即用的编程接口。

插件扩展层为 Nacos 提供了灵活的定制与集成能力。该层包含鉴权插件、加密插件、数据源插件、环境变量插件、限流插件,均支持用户以引入依赖的方式按需替换默认实现。扩展模块还提供了 Prometheus 指标暴露、Istio 服务网格集成,以及对关键操作链路的 Trace 埋点,便于与现有可观测体系对接。

公共基础设施层 位于最底层,为上层所有模块提供通用能力支撑。common 公共工具库封装了编解码工具,支持 JSON 与 Protobuf 两种序列化格式;提供 HTTP 客户端封装、线程工厂与线程池管理、事件通知机制、国际化支持,以及通用异常与错误码定义。api 模块保留旧版 API 定义以兼容历史版本,sys 模块提供系统级基础组件。

五层架构自上而下,每一层只依赖其下层提供的接口,不感知下层的具体实现细节。管理入口层负责对外暴露交互界面与 API,核心服务层处理业务逻辑与一致性协调,通信与客户端层封装网络交互细节,插件扩展层提供按需定制的扩展点,公共基础设施层则作为整个系统的通用底座。理解这五层划分及其关联,后续深入各模块源码时便能始终保有清晰的方向感。

项目架构:以项目打包发布的维度分层分析

从项目构建与打包发布的视角来看,Nacos 2.5.2 的源码可以按 Maven 模块间的依赖关系划分为六层,与功能分层的视角形成互补。

最顶层是 distribution 打包发布模块 ,不包含业务代码,仅负责将各子模块的编译产物组装为可直接运行的二进制包,提供 conf 示例配置和 bin 启动脚本,用户通过 startup.sh 即可启动 Nacos 服务。展示层console 后端控制台启动模块和 console-ui 前端控制台模块组成,负责加载所有子模块并对外提供管理页面。业务层 包含 config 配置中心和 naming 注册中心,是核心业务逻辑的直接承载者。能力层 为业务层提供底层支撑,包含一致性协议模块 consistency、数据持久化模块 persistence 和鉴权模块 auth通信层remote 通信抽象和 grpc 协议实现组成,承载 Client 与 Server 之间的所有网络交互。最底层是 common 公共基础设施模块,提供编解码、线程池、事件通知等通用工具,被所有上层模块依赖。

六层架构自下而上遵循严格的单向依赖:common 作为基底被所有层依赖,通信层依赖 common,能力层依赖通信层,业务层依赖能力层,展示层组装业务层,最终由 distribution 汇集全部模块完成打包发布。

三、为什么从HTTP转向gRPC:一切为了极致的性能

在分布式系统中,通信协议的选择直接决定了系统的吞吐量天花板与实时性下限。Nacos 2.x 全面从 HTTP 转向 gRPC,本质上是一次面向规模化场景的通信模型重构。要理解这个决策,需要先回到协议本身的特性差异上来。

HTTP 1.1:短连接模型的天然瓶颈

参考文档:www.rfc-editor.org/rfc/rfc9112...

HTTP 1.1 是最广泛使用的应用层协议,但它与生俱来的几个特性,使其在大规模微服务通信场景中力不从心。首先是短连接模式,每次 HTTP 请求都需要独立建立 TCP 连接,完成请求响应后再断开。TCP 三次握手和四次挥手的开销,在少量请求时微不足道,但当数千个客户端同时与服务端保持心跳通信时,连接的反复建立与销毁会迅速消耗服务端的 CPU 和文件描述符资源。其次,HTTP 1.1 的请求响应模型是单向的------客户端发请求,服务端返回响应------服务端无法主动向客户端推送数据。当配置发生变更时,服务端要么等待客户端下一次轮询,要么自行发起 HTTP 请求反向推送,前者存在延迟,后者依然逃不开短连接的开销。即便 HTTP 1.1 引入了 Keep-Alive 机制允许在单个连接上发送多次请求,但这种复用受限于请求串行处理,队头阻塞问题让并发效率大打折扣。

HTTP 2.0:长连接与多路复用的突破

参考文档:www.rfc-editor.org/rfc/rfc9113...

HTTP 2.0 在保持与 HTTP 1.1 语义兼容的前提下,对传输层做了彻底重构。它引入二进制分帧层,将请求和响应拆解为独立的帧,在一个 TCP 连接上交错传输,实现了真正的多路复用。一个连接上可以同时承载多个请求和响应,彼此独立、互不阻塞,解决了 HTTP 1.1 的队头阻塞问题。长连接模型下,TCP 连接只需建立一次,后续所有通信都在这条连接上完成,连接的创建/销毁成本大幅降低。服务端推送能力也被原生支持,服务端可以主动向客户端推送资源,不再受限于请求响应的单向模型。这些特性使得 HTTP 2.0 在性能和实时性上显著优于 HTTP 1.1。

gRPC:为微服务而生的通信框架

gRPC 是一个现代开源高性能 RPC 框架,官方定义将其描述为"可以在任何环境中运行,能够高效地连接数据中心内部和跨数据中心的服务,并支持负载均衡、链路追踪、健康检查和认证等可插拔能力,同时也适用于将设备、移动应用和浏览器连接到后端服务的最后一公里"。这一定义直接点明了 gRPC 的设计目标:构建一套通用、高性能、可扩展的服务间通信体系,而这正是微服务基础设施的核心诉求。

gRPC 由 Google 推出,底层基于 HTTP/2 协议传输,但它在 HTTP/2 的多路复用和长连接能力之上,进一步构建了一套完整的微服务通信体系。gRPC 使用 Protocol Buffers 作为接口定义语言和序列化协议,通过.proto文件精确定义服务方法、请求参数和返回类型,强类型的契约定义从协议层面消除了服务提供方和消费方之间的接口歧义。同时,Protocol Buffers 的二进制序列化相较于 JSON 等文本序列化格式,在数据体积和编解码性能上都有数量级的提升,这对于高频、高并发的服务间通信场景意义显著。HTTP/2 的多路复用和长连接能力被 gRPC 充分继承,单个 TCP 连接上可以同时承载多个并发请求,连接资源得到高效复用。

gRPC 最关键的差异化能力在于其双向流通信模型。HTTP 1.1 和 HTTP/2 的请求响应模型本质上是客户端主动、服务端被动,服务端推送只是 HTTP/2 的一种辅助机制。gRPC 则从根本上重新定义了客户端与服务端的交互关系,将通信模型抽象为四种调用方式:

一元调用,客户端发送一次请求、服务端返回一次响应,对应传统的请求响应模式;

服务端流式,客户端发送一次请求,服务端持续返回多个响应;

客户端流式,客户端持续发送多个请求,服务端最后返回一个响应;

双向流,客户端和服务端同时独立地发送和接收数据流,两个方向的流完全解耦。双向流的能力从根本上改变了推送的通信范式------服务端不需要等待客户端主动询问,就可以通过已建立的长连接随时向客户端推送消息。这在微服务架构中直接对应着配置变更实时推送、服务实例上下线通知、心跳保持与租约续约等核心通信场景,也是 Nacos 2.x 选择 gRPC 作为核心通信协议的根本原因。

Nacos 的选择:从 HTTP 短连接到 gRPC 长连接

理解了协议栈的演进,Nacos 2.x 的技术决策就变得清晰了。1.x 基于 HTTP 1.1 的通信模型面对大规模集群时,连接数膨胀、心跳风暴、推送延迟等问题逐步成为性能瓶颈。2.x 选择 gRPC 而不是直接使用 HTTP 2.0,核心原因在于 gRPC 在 HTTP 2.0 的传输能力之上,提供了开箱即用的双向流、强类型契约和经过大规模验证的连接管理机制,这些正是微服务基础设施最需要的能力。Client 与 Server 之间通过一条 gRPC 长连接承载所有通信,心跳、配置查询、服务注册、变更推送全部在这条连接上多路复用,连接资源消耗从与实例数线性相关降低为常量级,推送延迟从秒级降至毫秒级。这为 Nacos 走向更大规模集群奠定了通信层的核心基础。

四、gRPC 通信层源码剖析------Nacos 2.x 的性能基石

理解了 Nacos 的整体架构以及从 HTTP 转向 gRPC 的技术决策之后,接下来我们通过源码来了解Nacos对于gRPC通信处理的相关细节。

消息体与服务定义:有什么能力

这是所有 gRPC 开发的起点。你需要定义好客户端和服务端之间"说什么语言"。

首先是需要先编写 .proto 文件,定义消息体和服务。Nacos 的核心协议文件是 nacos_grpc_service.proto,它定义了两种核心消息体和服务:

message:

  • Message :通信的数据载体,类似 Java 的 POJO

  • 字段编号= 2= 3 是字段在二进制中的唯一标识,不可随意修改,决定了序列化/反序列化的兼容性。

  • Any 类型 :可以装载任意 Protobuf 消息,Nacos 用这个实现多态,metadata.type 决定了 body 里装的是什么具体请求/响应。

    service:

  • Request / Response 流:一元 RPC,用于客户端发送请求,服务端返回响应。例如服务注册、配置查询等。

  • BiRequestStream 流 :建立一个双向流通道,这是 Nacos 实现服务端主动推送(如配置变更通知、服务实例下线通知)的关键。

java 复制代码
yourPath/nacos-2.5.2/api/src/main/proto/nacos_grpc_service.proto
​
syntax = "proto3"; // 声明使用proto3语法
​
import "google/protobuf/any.proto"; // 导入Google的Any类型,用于表示任意类型的消息体。在Nacos中,这是实现请求/响应体多态的关键,服务发现、配置管理等不同模块的具体数据都通过它来承载
​
import "google/protobuf/timestamp.proto"; // 导入Google的时间戳类型,用于表示精确的时间点
​
option java_multiple_files = true; // 为每个消息/服务生成一个单独的Java文件
option java_package = "com.alibaba.nacos.api.grpc.auto"; // 指定生成Java代码到该路径下
​
message Metadata { // 定义元数据消息,用于承载请求的上下文信息
  string type = 3;  // 请求类型,用于区分是配置查询(ConfigQuery)、服务注册(InstancePublish)还是其他类型的请求。这是 Nacos 服务端进行路由分发的核心依据
  string clientIp = 8; // 客户端IP地址,服务端通过它来识别请求来源,实现IP白名单、灰度发布等功能
  map<string, string> headers = 7;  // 请求头,以键值对形式存储
}
​
message Payload { // 定义通信的有效载荷
  Metadata metadata = 2; // 请求/响应的元数据,包含此次通信的上下文信息
  google.protobuf.Any body = 3; // 请求/响应的具体内容。这是一个Any类型,可以装载任何符合protobuf定义的消息。
  // 例如,当 metadata.type = "ConfigQuery"时,body装载的是ConfigQueryRequest/Response
}
​
service Request {
  // 发送一个普通请求,并同步等待响应
  rpc request (Payload) returns (Payload) {
  }
}
​
service BiRequestStream {
  // 建立一个双向流连接。客户端和服务端可以互相发送一系列 Payload 消息。
  // 这个连接是 Nacos 2.x 性能的基石,由 BiRequestStreamServerImpl 负责管理。
  // 它的核心作用是:
  // 1. 服务端可以主动推送配置变更、服务实例上下线等通知给客户端,无需客户端轮询。
  // 2. 客户端可以复用此连接发送心跳,大幅减少频繁建立和销毁连接的开销。
  rpc requestBiStream (stream Payload) returns (stream Payload) {
  }
}

编译生成gRPC代码:根据定义生成java代码

在执行编译生成gRPC前,需要先阅读api模块下的maven依赖及相关代码生成插件。

这里需要注意的是,通常定义好消息体和服务后,需要执行gRPC代码生成插件,在/target的目录下生成对应的java代码。

这里由于Nacos为了让开发者拿到 Nacos 源码后,能开箱即用地启动核心服务,免除开发者安装 protoc 编译环境,避免因 protoc 版本不一致等环境问题导致项目无法编译,因此直接将生成好的gRPC-java代码放到了yourPath/nacos-2.5.2/api/src/main/java/com/alibaba/nacos/api/grpc/auto目录下,这也解释了为什么在"快速开始:Idea启动Nacos 2.5.2"小节中没有单独执行maven install,代码却没有爆红。

xml 复制代码
yourPath/nacos-2.5.2/api/pom.xml
​
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-all</artifactId>
        <version>${revision}</version>
    </parent>
    
    <modelVersion>4.0.0</modelVersion>
    
    <artifactId>nacos-api</artifactId>
    <packaging>jar</packaging>
    
    <name>nacos-api ${project.version}</name>
    <url>https://nacos.io</url>
    <description>Nacos api pom.xml file</description>
    <build>
      // 执行gRPC代码生成的相关插件,如果需要生成grpc代码可以放开,如果这里放开后编译还是报错,可以参考consistency模块,需要注意引入extensions标签相关内容
        <plugins> 
            <!--  reuse when you need to update grpc model  -->
             <!--<plugin>
                 <groupId>org.xolstice.maven.plugins</groupId>
                 <artifactId>protobuf-maven-plugin</artifactId>
                 <version>0.5.0</version>
                 <configuration>
                     <protocArtifact>com.google.protobuf:protoc:3.8.0:exe:${os.detected.classifier}</protocArtifact>
                     <pluginId>grpc-java</pluginId>
                     <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.14.0:exe:${os.detected.classifier}</pluginArtifact>
                 </configuration>
                 <executions>
                     <execution>
                         <goals>
                             <goal>compile</goal>
                             <goal>compile-custom</goal>
                         </goals>
                     </execution>
                 </executions>
             </plugin>-->
        </plugins>
    </build>
    
    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <scope>test</scope>
        </dependency>
      // gRPC的网络传输层。基于Netty实现HTTP/2通信,负责客户端和服务端之间真正的字节流收发
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
        </dependency>
      // gRPC和Protobuf之间的桥接层。提供ProtoUtils工具类、Marshaller等,让gRPC能够把Protobuf的Message对象序列化成网络传输的字节流。
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
      // gRPC的Stub(桩)基类。代码里用到的RequestBlockingStub、RequestFutureStub、AbstractStub等客户端 Stub,以及服务端的BindableService接口,都来自这个包。它是*Grpc.java生成代码的父类来源。
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>
      // gRPC 的工具类库。包含一些辅助功能,如超时控制Deadline、并发工具等。
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-util</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.api.grpc</groupId>
            <artifactId>proto-google-common-protos</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
        </dependency>
    
    </dependencies>
</project>
​

下图展示Nacos将生成好的gRPC-java代码放到了yourPath/nacos-2.5.2/api/src/main/java/com/alibaba/nacos/api/grpc/auto目录下。

如果读者想自己动手生成gRPC-java代码,可以根据下图设置api模块下的pom文件(将consistency模块的extensions和plugins标签拷贝覆盖到该pom文件),然后在maven控制面板找到api模块下的protobuf插件,编译即可生成,也可以通过编译整个api模块来生成代码。

Stub(存根):谁来调用

Stub 是 gRPC 编译器根据 .proto 文件自动生成的客户端代理类。对开发者来说,调用 Stub 的方法就跟调用本地方法一样------传入请求对象,拿到响应对象,完全不需要关心底层的网络传输、序列化、HTTP2 帧解析等细节。

typescript 复制代码
yourPath/nacos-2.5.2/api/src/main/java/com/alibaba/nacos/api/grpc/auto/RequestGrpc.java
​
// 创建一个支持该服务所有调用类型的异步 Stub
public static RequestStub newStub(io.grpc.Channel channel) {
  // 定义 Stub 工厂,匿名内部类实现了 StubFactory 接口,用于创建 RequestStub 实例
    io.grpc.stub.AbstractStub.StubFactory<RequestStub> factory =
      new io.grpc.stub.AbstractStub.StubFactory<RequestStub>() {
        @Override
        public RequestStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
            // 每次需要新 Stub 时(比如设置超时、添加拦截器),都会调用这个方法
            // Channel:底层传输通道(TCP/HTTP2 连接)
            // CallOptions:调用选项(截止时间、认证信息、元数据等)
            return new RequestStub(channel, callOptions);
        }
      };
    
    // 通过工厂创建 Stub 实例
    return RequestStub.newStub(factory, channel);
}
​
// 创建一个阻塞式 Stub,支持一元 RPC 和服务端流式调用
public static RequestBlockingStub newBlockingStub(
    io.grpc.Channel channel) {
    // 定义阻塞式 Stub 工厂 
    io.grpc.stub.AbstractStub.StubFactory<RequestBlockingStub> factory =
      new io.grpc.stub.AbstractStub.StubFactory<RequestBlockingStub>() {
        @Override
        public RequestBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
            return new RequestBlockingStub(channel, callOptions);
        }
      };
    
    return RequestBlockingStub.newStub(factory, channel);
}

Channel(通道): 建立连接通道

Channel 是客户端与服务端之间的虚拟连接,底层基于 TCP/HTTP2 协议。过程遵循"先握手、后建流"的原则:

  1. 客户端根据服务端 IP 和 gRPC 端口**(主端口 + 1000,如 8848 → 9848)创建 ManagedChannel,建立底层 TCP 连接。

  2. 基于这个 Channel 创建一元 RPC 的 FutureStub,发起 ServerCheck 健康检查请求,获取服务端分配的 connectionId

  3. 健康检查通过后,在同一个 Channel 上再创建双向流的 BiRequestStreamStub,调用 requestBiStream() 建立长连接流,并通过 ConnectionSetupRequest 完成客户端版本号、能力表、租户信息的上报。

    一旦双向流建立成功,后续客户端与服务端之间的所有通信都复用这个Channel,服务注册、配置查询、心跳保活、服务端推送等操作全部走同一条 TCP 连接,避免了频繁创建和销毁连接的开销。这也是 Nacos 2.x 相比于 1.x HTTP 短连接模式性能大幅提升的根本原因。

scss 复制代码
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#connectToServer
​
@Override
public Connection connectToServer(ServerInfo serverInfo) {
    // 待分配的最新连接 ID,由服务端在握手时返回
    String connectionId = "";
    
    // 建立gRPC Channel,计算gRPC端口:服务端主端口 + 偏移量(默认 +1000,即 8848 → 9848)
    int port = serverInfo.getServerPort() + rpcPortOffset();
    // 创建一个ManagedChannel,底层建立 TCP/HTTP2 连接,可配置TLS或明文传输(默认为明文)
    ManagedChannel managedChannel = createNewManagedChannel(serverInfo.getServerIp(), port);
    // 在一元RPC通道上创建异步Stub,用于后续的serverCheck
    RequestGrpc.RequestFutureStub newChannelStubTemp = createNewChannelStub(managedChannel);
    
    // 服务端健康检查(握手),向服务端发送ServerCheckRequest,验证服务端是否存活且兼容
    Response response = serverCheck(serverInfo.getServerIp(), port, newChannelStubTemp);
    // 获取服务端返回的响应,其中包含服务端分配的唯一连接ID
    ServerCheckResponse serverCheckResponse = (ServerCheckResponse) response;
    connectionId = serverCheckResponse.getConnectionId();
    
    // 创建双向流 Stub,复用上面已经建立的ManagedChannel,创建双向流的异步Stub,双向流模式下,客户端和服务端可以同时独立地发送和接收消息
    BiRequestStreamGrpc.BiRequestStreamStub biRequestStreamStub = BiRequestStreamGrpc.newStub(
            newChannelStubTemp.getChannel());
    
    // 封装连接对象, 创建一个GrpcConnection,grpcExecutor处理双向流消息线程池
    GrpcConnection grpcConn = new GrpcConnection(serverInfo, grpcExecutor);
    // 设置服务端返回的连接 ID,后续所有通信都需要带上这个 ID 来标识身份
    grpcConn.setConnectionId(connectionId);
    
    // 绑定双向流(重要),内部实现如下:
    // 1. 调用 biRequestStreamStub.requestBiStream(observer),发起双向流 RPC
    // 2. 返回一个 StreamObserver<Payload>,客户端通过它向服务端发送消息
    // 3. 传入的 grpcConn 实现了 StreamObserver 接口,用于接收服务端推送的消息
    StreamObserver<Payload> payloadStreamObserver = bindRequestStream(biRequestStreamStub, grpcConn);
    
    // 完善连接对象,将payloadStreamObserver设置到连接中,后续通过它发送请求
    grpcConn.setPayloadStreamObserver(payloadStreamObserver);
    // 保留一元RPC的异步 Stub,用于后续的配置查询、服务注册等一次性请求
    grpcConn.setGrpcFutureServiceStub(newChannelStubTemp);
    // 保留Channel引用,用于关闭连接
    grpcConn.setChannel(managedChannel);
    
    // 发送连接建立请求,构造ConnectionSetupRequest,告诉服务端自己的身份和能力
    ConnectionSetupRequest conSetupRequest = new ConnectionSetupRequest();
    // 客户端版本号,服务端可以据此做兼容性处理
    conSetupRequest.setClientVersion(getClientVersion());
    // 客户端自定义标签,用于灰度发布、机房标识等场景
    conSetupRequest.setLabels(super.getLabels());
    // 设置客户端的能力表:声明自己支持哪些功能,比如是否支持批量注册、是否支持模糊查询等
    conSetupRequest.setAbilityTable(
            NacosAbilityManagerHolder.getInstance().getCurrentNodeAbilities(abilityMode()));
    // 租户信息(命名空间 ID),用于多租户隔离
    conSetupRequest.setTenant(super.getTenant());
    // 通过双向流通道发送连接建立请求,完成最后的握手和注册
    grpcConn.sendRequest(conSetupRequest);
    
    // 返回封装好的连接对象,上层通过它来与服务端通信
    return grpcConn;
}

Server(服务端): 谁来处理

请求到了服务端之后,由 gRPC Server 负责接收和分发。服务端基于Netty构建,在 BaseGrpcServer.startServer() 中完成了服务注册、TLS协议、数据压缩/解压、心跳保活等相关处理,服务端启动后,Netty开始监听gRPC端口,接收客户端连接,并根据请求类型将消息分发到对应的 Service 实现类中处理。

scss 复制代码
com.alibaba.nacos.core.remote.grpc.BaseGrpcServer#startServer
​
@Override
public void startServer() throws Exception {
    // 创建可变的服务处理器注册表
    final MutableHandlerRegistry handlerRegistry = new MutableHandlerRegistry();
    
    // 注册gRPC服务实现
    addServices(handlerRegistry, getSeverInterceptors().toArray(new ServerInterceptor[0]));
    
    // 创建Netty服务端构建器,监听端口,默认为主端口 + 1000(即 8848 → 9848)
    NettyServerBuilder builder = NettyServerBuilder.forPort(getServicePort()).executor(getRpcExecutor());
    
    // 配置TLS协议协商器,如果配置了TLS证书,加入到builder中,启用 TLS 加密传输,否则使用使用明文传输(HTTP/2 的 h2c 模式)
    Optional<InternalProtocolNegotiator.ProtocolNegotiator> negotiator = newProtocolNegotiator();
    if (negotiator.isPresent()) {
        InternalProtocolNegotiator.ProtocolNegotiator actual = negotiator.get();
        Loggers.REMOTE.info("Add protocol negotiator {}", actual.getClass().getCanonicalName());
        builder.protocolNegotiator(actual);
    }
    
    // 添加传输层过滤器,用于连接和日志管理
    for (ServerTransportFilter each : getServerTransportFilters()) {
        builder.addTransportFilter(each);
    }
    
    // 配置并构建 Server
    server = builder
        .maxInboundMessageSize(getMaxInboundMessageSize()) // 可通过环境变量配置,默认10MB
        .fallbackHandlerRegistry(handlerRegistry) // 请求的服务方法找不到时的回调兜底
        .compressorRegistry(CompressorRegistry.getDefaultInstance()) // 压缩响应数据
        .decompressorRegistry(DecompressorRegistry.getDefaultInstance()) // 如果客户端压缩了请求体,这里进行解压
        .keepAliveTime(getKeepAliveTime(), TimeUnit.MILLISECONDS) // 服务端向客户端发送心跳的间隔时间
        .keepAliveTimeout(getKeepAliveTimeout(), TimeUnit.MILLISECONDS) // 心跳超时时间
        .permitKeepAliveTime(getPermitKeepAliveTime(), TimeUnit.MILLISECONDS) // 没有活跃的Stream,也允许发送心跳的最小间隔
        .build();
    
    // 启动
    server.start();
}

序列化:怎么传

gRPC 默认使用Protocol Buffers作为序列化协议。相比传统的JSON,Protobuf编码后的数据体积更小、解析速度更快,特别适合微服务场景下的高频通信。在"消息体服务与定义"小节里,通信载体 Payloadbody 字段使用了 google.protobuf.Any 类型,可以装载任意 Protobuf 消息。服务端收到请求后,通过 PayloadRegistry(类型注册表)根据 metadata.type 查找到对应的具体消息类型,再将 Any 反序列化为 ConfigQueryRequestInstancePublishRequest 等具体对象------这种"类型路由 + 动态反序列化"的机制,让 Nacos 所有业务模块得以复用同一条 gRPC 通道。

流程串联:梳理gRPC开发及执行流程

理解了gRPC的设计理念之后,我们通过一张流程图来直观地梳理gRPC的开发步骤与请求执行的全链路。上图从.proto协议定义文件开始,一直到一次完整的RPC调用结束,覆盖了开发阶段和运行时阶段两个维度。

一切从Proto File开始。开发者需要编写.proto文件来定义gRPC服务的接口契约,其中包括消息体和服务结构。这一步是gRPC开发的起点,也是服务提供方和消费方之间唯一的"协议共识"。以Nacos的配置查询为例,需要在.proto中声明service服务和message消息,消息中的每个字段都需要指定类型和字段编号。这份.proto文件就是后续所有代码生成的唯一输入源。有了协议定义之后,protoc编译器介入,根据.proto文件自动生成对应语言的代码。产出的代码分为两个分支:一边是客户端的Client Stub,它封装了远程调用的序列化与网络传输逻辑,让本地调用看起来就像调用一个本地方法;另一边是服务端的Server Service Interface,它是一个抽象接口,开发者只需要实现这个接口来编写具体的业务逻辑,而网络接收、请求分发、序列化与反序列化全部由gRPC框架自动完成。

进入运行时阶段,客户端通过Stub发起远程调用,Stub将请求消息序列化为二进制数据,通过Channel发送出去。Channel底层基于TCP连接,使用HTTP/2协议与Server通信,多路复用让一条连接上可以同时承载多个并发请求。服务端收到数据后,gRPC框架自动完成反序列化,将二进制数据还原为服务端代码可以直接处理的内存对象,然后根据请求中的方法名分发到对应的Service实现上。业务逻辑处理完毕后,服务端将响应消息序列化,沿原路通过HTTP/2连接返回给客户端。客户端Channel接收到响应数据后,Stub完成反序列化,将结果以方法返回值的正常形式交还给调用方。整个过程中,开发者只需要关注三步:定义.proto文件、实现服务端接口、通过Stub发起调用,其余的网络传输、序列化、流控、错误处理全部由gRPC框架承担,这就是RPC框架的核心价值所在。

书籍参考:

《gRPC: Up and Running: Building Cloud Native Applications with Go and Java for Docker and Kubernetes》

------Kasun Indrasiri、Danesh Kuruppu

五、全文小节

该篇以Nacos 2.5.2架构入手,从整体架构、源码架构、项目架构三个维度阐述了Nacos的核心设计思想,帮助读者快速建立对 Nacos 的全局认知体系,为后续深入学习各模块源码提供清晰的地图。

Nacos在微服务体系中承担配置中心与注册中心两项核心职能,架构上天然划分为Client与Server两类角色。从源码功能维度看,系统分为五层:管理及入口层负责对外暴露控制台与API,核心服务层承载配置与注册的业务逻辑,通信与客户端层基于gRPC封装网络交互,插件扩展层提供按需定制能力,公共基础设施层为全局提供通用工具支撑。从项目打包维度看,distribution模块将各层编译产物组装为可运行二进制包,依赖关系自下而上严格单向。

Nacos 2.x最核心的技术决策是从HTTP短连接全面转向gRPC长连接。1.x基于HTTP 1.1的通信模型在大规模集群下面临连接数膨胀、心跳风暴和推送延迟三重瓶颈------短连接模式下每次请求独立建连拆连,服务端资源消耗与实例数线性相关;单向请求响应模型使服务端无法主动推送,配置变更依赖客户端轮询,延迟达到秒级。2.x选择gRPC而非直接使用HTTP/2,关键在于gRPC在HTTP/2的多路复用和长连接之上,提供了双向流通信、Protocol Buffers强类型契约和开箱即用的连接管理。一条gRPC长连接承载所有心跳、查询、注册和推送,连接资源消耗降为常量级,推送延迟从秒级压缩到毫秒级,单机可支撑的实例数量提升了一个数量级。

在gRPC通信层的源码实现中,Nacos通过nacos_grpc_service.proto定义了Message元数据载体、Payload通信体以及Request和BiRequestStream两种服务模型。客户端通过connectToServer方法完成TCP建连、健康检查握手和双向流绑定,服务端基于Netty构建gRPC Server,在startServer中完成服务注册、TLS配置和心跳保活。Payload.body使用Any类型实现多态,PayloadRegistry根据metadata.type完成类型路由与动态反序列化,使所有业务模块得以复用同一条gRPC通道。

相信读完本篇的读者已经对Nacos的架构设计和gRPC有了一定的理解,下篇作者将围绕Nacos服务注册与发现展开梳理源码,深入剖析 Nacos 在微服务体系中的核心职责。 敬请期待《第2篇:Nacos 服务注册与发现------从客户端调用到服务端存储》

原创不易,如果本文对您有帮助,带来了些许灵感或启发,烦请动动小手点赞、关注、转发、收藏。这是作者持续更新的动力源泉,衷心感谢您的支持。我会尽量在工作之余,为大家带来更高质量的内容,努力保持周更。

相关推荐
爱笑的源码基地11 小时前
拿来即用:基于Spring Cloud+UniApp的智慧工地源码,架构清晰易扩展
java·云计算·源码·智慧工地·程序·开箱即用·数字工地
冬奇Lab21 小时前
RAG 系列(十七):Agentic RAG——让 Agent 主导检索过程
人工智能·llm·源码
谙弆悕博士1 天前
【附C++源码】从零开始实现 2048 游戏
java·c++·游戏·源码·项目实战·2048
幽络源小助理1 天前
最新轻量美化表白墙系统源码v2.0_带后台版_附搭建教程
前端·开源·源码·php源码
幽络源小助理2 天前
IP定位系统源码二开版 新增分销功能 PHP地理位置查询系统
前端·开源·源码·php源码
不会敲代码13 天前
手写 Zustand:三十分钟带你搞懂状态管理库的核心原理
前端·javascript·源码
坐吃山猪3 天前
【Hanako】README02_LEVEL2_Agent编排机制
源码·agent·hanako
wuyoula3 天前
如何在捷云鲸论坛高效获取高质量技术解答?
服务器·c++·人工智能·tcp/ip·源码
幽络源小助理5 天前
苹果CMS V10 MXPro V4.5模版下载, 自适应视频主题源码, 幽络源源码
前端·开源·源码·php源码