Nacos 集群内部 RPC 规范深度解析:ClusterRpcClientProxy、请求处理器与内网认证体系
【免费下载链接】nacosan easy-to-use dynamic service discovery, configuration and service management platform for building AI cloud native applications.项目地址: https://gitcode.com/GitHub_Trending/na/nacos
导读
本文基于 Nacos 官方设计文档 foundation-internal-rpc-spec.md,系统讲解 Nacos 集群节点之间 server-to-server RPC 的传输模型、调用者模型、处理器注册与来源限制、内网 API 认证与请求过滤器、Payload 注册规则、集群请求分类以及超时与重试语义。读者将掌握 Nacos 集群节点间通知、查询、校验与状态同步请求如何被构造、鉴权、分发与处理,并能结合仓库源码(ClusterRpcClientProxy、RequestHandlerRegistry、RemoteRequestAuthFilter等)理解其底层实现原理,为二次开发与生产排障提供依据。
1. 适用范围:什么是 Nacos 内部 RPC
Nacos 内部 RPC(Internal RPC)是 Nacos 服务端节点之间请求的传输与处理器模型。当 Core、Config、Naming 等领域需要通知、查询、校验或同步与其他成员之间的状态时,走的就是这套内部 RPC。它属于基础设施层(foundation-level)规范,是 Foundation Capabilities Spec 中内部 RPC 部分的展开,并依赖以下规范:
- Cluster Membership Spec(成员关系)
- Remote Connection Lifecycle Spec(远程连接生命周期)
- Request Filtering And Runtime Context Spec(请求过滤与运行时上下文)
- gRPC API Spec(gRPC 接口)
本规范不定义AP/CP 一致性算法本身。Distro 数据归属、Raft 组行为、Config dump 顺序、Naming 反熵规则由使用该内部 RPC 基础的领域或一致性规范定义;共享 Config dump 与本地缓存边界由 Persistence And Dump Spec 定义。
2. 设计模型:复用 SDK 的 Payload 信封
Nacos 内部 RPC 与 SDK 面向的 gRPC API共用同一个固定 gRPCPayload信封。业务含义由Payload.metadata.type选择,body 中存放 JSON 序列化后的Request或Response对象。
集群 gRPC 服务端使用cluster来源标签,ClusterRpcClientProxy创建的 server-to-server 客户端同样携带该标签:
RemoteConstants.LABEL_SOURCE = RemoteConstants.LABEL_SOURCE_CLUSTER在源码 RemoteConstants.java 中可以看到这三个常量的真实取值:LABEL_SOURCE = "source"、LABEL_SOURCE_SDK = "sdk"、LABEL_SOURCE_CLUSTER = "cluster"。集群服务端 GrpcClusterServer.java 的getSourceLabel()直接返回RemoteConstants.LABEL_SOURCE_CLUSTER。
因此,大多数内部 RPC 并不是一套独立的协议族,而是在远程层之上施加"受限来源 + 鉴权 + 处理器纪律"的约束。唯一的例外是内置的 JRaft CP 传输:它使用 JRaft 原生的 gRPC 消息与处理器,而非 Nacos 的Payload与RequestHandler模型,但它仍属于内部 server-to-server API,并遵守本规范中的服务端身份规则。
整体链路模型为:
member view -> ClusterRpcClientProxy per-member gRPC client -> Payload(metadata.type = request simple class name) -> RequestHandlerRegistry -> Request filters -> domain RequestHandler -> Response payload3. 调用者模型:ClusterRpcClientProxy 的职责
ClusterRpcClientProxy是内部 RPC 的调用入口,位于 ClusterRpcClientProxy.java。它是 Spring@Service,同时继承MemberChangeListener,与成员管理深度绑定。
3.1 客户端生命周期
从源码看,其核心规则可以逐条对应实现:
- 按成员建客户端:每个远端成员对应一个集群 RPC 客户端,客户端 key 固定为
Cluster-{member.address}(见memberClientKey(Member)方法); - 单地址路由:每个集群客户端通过
ServerListFactory只路由到恰好一个成员地址(源码中匿名ServerListFactory的genNextServer()、getCurrentServer()、getServerList()均只返回该成员地址); - 成员变更刷新:
init()在@PostConstruct时通过NotifyCenter.registerSubscriber(this)订阅成员变更,onEvent(MembersChangeEvent event)回调中重新获取allMembersWithoutSelf()并调用refresh(members);refresh会为新成员创建客户端,并销毁已离开成员的Cluster-前缀客户端(RpcClientFactory.getClient(...).shutdown()); - 发送前注入身份头:
sendRequest/asyncRequest在真正发出请求前都会调用injectorServerIdentity(request),通过AuthHeaderUtil.addIdentityToHeader注入配置的服务端身份; - 三种调用方式:同步请求
sendRequest(Member, Request[, timeoutMills])(默认超时DEFAULT_REQUEST_TIME_OUT = 3000L毫秒)、带回调的异步请求asyncRequest(Member, Request, RequestCallBack)、以及向allMembersWithoutSelf()广播的sendRequestToAllMembers(Request)。
3.2 调用者规则
规范要求调用方必须容忍:成员变化、客户端缺失、客户端已停止、超时以及过期成员状态。例如sendRequest在目标客户端不存在时会抛出NacosException(CLIENT_INVALID_PARAM, "No rpc client related to member: ...");isRunning(Member)方法用于在领域行为需要时检查目标客户端是否连接运行。
领域调用方应在领域行为需要时检查目标成员是否存在、成员状态、能力以及客户端运行状态。一次成功的 RPC 只意味着远端处理器按自身契约接受并处理了请求,并不代表集群级收敛——除非领域协议明确如此声明。
4. 处理器注册与来源限制
服务端处理器继承RequestHandler<T extends Request, S extends Response>。RequestHandlerRegistry(位于 RequestHandlerRegistry.java)实现ApplicationListener<ContextRefreshedEvent>,在 Spring 上下文刷新后扫描所有RequestHandler类型的 Bean,按请求类 simple name 注册(registryHandlers.putIfAbsent(tClass.getSimpleName(), requestHandler)),并维护sourceRegistry记录来源限制。
4.1 @InvokeSource 来源限制
@InvokeSource注解定义在 InvokeSource.java,只有一个String[] source()属性。RequestHandlerRegistry.onApplicationEvent中,若处理器类标注了@InvokeSource,则把允许的来源集合写入sourceRegistry(key 为请求类 simple name);checkSourceInvokeAllowed(type, source)方法在后续请求分发时校验:
public boolean checkSourceInvokeAllowed(String type, String source) { if (sourceRegistry.containsKey(type) && !sourceRegistry.get(type).contains(source)) { return false; } return true; }处理器规则总结:
- 一个 Payload 类型在一个服务端运行时中应只有一个语义处理器;
- 集群专用处理器必须用
@InvokeSource声明cluster来源;声明后,来自其他来源标签的调用会在请求解析到达处理器之前被拒绝; - 若处理器未声明
@InvokeSource,注册表不强制来源限制,因此新增内部处理器应显式声明; - 处理器来源限制是传输层保护,绝不能替代鉴权或权限检查。
作为对照,RequestHandlerRegistryTest.java 与 ClusterRpcClientProxyTest.java 覆盖了注册、来源校验与客户端刷新行为,可作为理解该机制的测试佐证。
4.2 处理器拥有语义,远程层拥有分发
规范强调:处理器拥有请求语义;远程基础层拥有分发、来源检查、请求上下文、过滤器与响应转换。这意味着领域开发者只关心handle(Request, RequestMeta)的业务逻辑,传输层面的纪律由基础设施统一保证。
5. 安全与请求过滤器
内部 RPC 必须使用与其他受保护 gRPC 请求相同的安全模型。核心规则:
- 内部集群 API 应在
handle(...)上标注@Secured(apiType = ApiType.INNER_API); - 领域专属内部 API 应在鉴权插件需要类型化资源时设置
signType或资源信息; ClusterRpcClientProxy必须注入配置的服务端身份头;RemoteRequestAuthFilter在内网 API 鉴权启用时校验内部 API 的服务端身份;- 公共 API 关闭鉴权不得被当作跳过必需内部服务端身份校验的理由;
- 命名空间校验、参数提取、TPS 控制等请求过滤器应在领域需要时声明在处理器上。
5.1 RemoteRequestAuthFilter 的实现
RemoteRequestAuthFilter.java 继承AbstractRequestFilter,其filter(...)逻辑与规范逐条对应:
- 通过反射获取处理器
handle方法,检查是否标注@Secured; - 将
secured.apiType()写入RequestContext的AuthContext; - 滚动升级兼容:若
ApiType.INNER_API且innerApiAuthEnabled.isEnabled()为 false,则直接放行(旧版本 Nacos 可能对部分内部 API 不带服务端身份,遵循旧逻辑); - 非 INNER_API 且公共鉴权关闭时放行;
- 调用
protocolAuthService.checkServerIdentity(request, secured)校验服务端身份,FAIL直接返回NO_RIGHT错误响应,MATCHED放行; - 之后进入常规身份与权限校验(
parseResource/parseIdentity/validateIdentity/validateAuthority),失败抛出AccessException并转为错误响应。
5.2 InnerApiAuthEnabled 仅是兼容门
InnerApiAuthEnabled只是 2.x 到 3.x 滚动升级的兼容开关,已标记为在 Nacos 3.4.0 移除;持久的内部 API 服务端身份校验绝不能依赖这个临时组件。
5.3 已知请求过滤器关注点
| 过滤器关注点 | 典型注解或路径 | 规则 |
|---|---|---|
| 服务端身份与鉴权 | @Secured、RemoteRequestAuthFilter | 保护内部 API 并解析鉴权上下文。 |
| 参数校验 | @ExtractorManager.Extractor | 复用共享参数提取与校验。 |
| 命名空间校验 | @NamespaceValidation | 资源标识含命名空间时校验其存在性。 |
| TPS 控制 | @TpsControl | 为集群流量注册并执行稳定的控制点。 |
RequestHandlerRegistry在注册时还会检测handle方法上的@TpsControl注解,并在TpsControlConfig.isTpsControlEnabled()开启时通过ControlManagerCenter注册 TPS 控制点——这印证了 TPS 控制是注册期与运行期双层生效的。
5.4 JRaft 原生 gRPC 的凭据校验
JRaft 原生 gRPC 鉴权遵循传输特有的规则:
- 每个新建 JRaft 客户端通过 gRPC
CallCredentials附加配置的 Nacos 服务端身份; - gRPC
ServerInterceptor在任何 Raft、CLI、snapshot、read、write 处理器执行前校验凭据; - 该路径不引入Nacos 的
RequestHandler或 payload 包装; - 凭据复用
nacos.core.auth.server.identity.key与nacos.core.auth.server.identity.value,且独立于公共 API 鉴权开关强制要求; - 固定的小写元数据名
nacos-server-identity-key和nacos-server-identity-value承载配置的 key 与 value,这样运维自定义的身份 key 不会被误解释为 gRPC 元数据名; - 日志绝不能打印身份 value。
这些元数据常量可在 JRaftAuthMetadata.java 中直接看到:
static final Metadata.Key<String> IDENTITY_KEY = Metadata.Key.of( "nacos-server-identity-key", Metadata.ASCII_STRING_MARSHALLER); static final Metadata.Key<String> IDENTITY_VALUE = Metadata.Key.of( "nacos-server-identity-value", Metadata.ASCII_STRING_MARSHALLER);对应的NacosJRaftCallCredentials与NacosJRaftServerInterceptor位于 raft/auth 目录,并有 NacosJRaftCallCredentialsTest.java 与 NacosJRaftServerInterceptorTest.java 佐证。
重要边界:凭据是请求鉴权,不是传输机密性。明文 JRaft 通道对路径上的攻击者仍可被观测和重放;需要此类保护的部署必须加固传输边界。滚动升级兼容与强制转换规则由 CP Consistency Spec 负责。
6. Payload 注册:处理器不是完整的 gRPC 契约
仅有请求处理器 Bean不足以构成有效的 gRPC payload 契约。请求与响应 payload 类必须注册,远程层才能解析Payload.metadata.type。
6.1 注册规则
- 请求与响应类必须继承 Nacos 远程
Request与Response模型; - payload 类必须保持 JSON 兼容;
- payload 类必须注册到合适的 payload 注册表中,或在
META-INF/services/com.alibaba.nacos.api.remote.Payload文件中登记,之后才能被视为有效的 gRPC 契约; - 新请求类必须通过
Request#getModule()返回稳定的模块名; - 注册 payload 并不意味着成为公共 API,除非接口规范明确暴露。
仓库中真实的注册文件位于 api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload,其中可以看到ConfigChangeClusterSyncRequest、ServerReloadRequest、ServerLoaderInfoRequest、各 Naming 请求、AI 领域请求等均已登记。如果代码中存在处理器但 payload 类型未注册,该处理器必须被当作未激活或不完整的 gRPC 契约。
6.2 一个真实的处理器样例
以 Config 领域为例,ConfigChangeClusterSyncRequestHandler.java 完整展示了本规范所有要素的落点:
@Since("2.0.0") @Component @InvokeSource(source = {RemoteConstants.LABEL_SOURCE_CLUSTER}) public class ConfigChangeClusterSyncRequestHandler extends RequestHandler<ConfigChangeClusterSyncRequest, ConfigChangeClusterSyncResponse> { private final DumpService dumpService; @Override @NamespaceValidation @TpsControl(pointName = "ClusterConfigChangeNotify") @ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class) @Secured(signType = SignType.CONFIG, apiType = ApiType.INNER_API) public ConfigChangeClusterSyncResponse handle( ConfigChangeClusterSyncRequest configChangeSyncRequest, RequestMeta meta) throws NacosException { ParamUtils.checkParam(configChangeSyncRequest.getTag()); DumpRequest dumpRequest = DumpRequest.create(configChangeSyncRequest.getDataId(), configChangeSyncRequest.getGroup(), configChangeSyncRequest.getTenant(), configChangeSyncRequest.getLastModified(), meta.getClientIp()); dumpRequest.setGrayName(configChangeSyncRequest.getGrayName()); dumpService.dump(dumpRequest); return new ConfigChangeClusterSyncResponse(); } }这段代码把@InvokeSource(cluster)(来源限制)、@Secured(INNER_API, signType = CONFIG)(内网鉴权与类型化资源)、@NamespaceValidation、@TpsControl、@ExtractorManager.Extractor(参数提取校验)全部叠加在同一个handle上,正是本规范第 4、5、6 节的直接实现样例。
7. 当前集群请求分类
规范汇总了当前仓库中已存在的集群请求类别:
| 类别 | 请求类型 | 归属 | 契约说明 |
|---|---|---|---|
| 成员上报 | MemberReportRequest、MemberReportResponse | Core cluster | 向对端上报成员元数据,将对端视图标记为UP,重置失败状态,并返回接收方自身的 self member。详细成员规则见 Cluster Membership Spec。 |
| 服务端远程上下文 | ServerReloadRequest、ServerReloadResponse、ServerLoaderInfoRequest、ServerLoaderInfoResponse | Core remote | 重载远程协议上下文,或查询另一节点的连接与负载指标。 |
| 配置变更同步 | ConfigChangeClusterSyncRequest、ConfigChangeClusterSyncResponse | Config | 通知对端配置变更,使其刷新 dump 与监听可见状态。Config Notify 语义见 AP Consistency Spec,Config 资源语义由 Config 规范定义。 |
| Naming Distro 传输 | DistroDataRequest、DistroDataResponse | Naming 与 Distro | 在节点间承载 Distro verify、snapshot、sync、delete、query 操作。Distro 归属与收敛规则见 AP Consistency Spec 与 Naming 规范。 |
| 插件可用性 | PluginAvailabilityRequest、PluginAvailabilityResponse | Core plugin | 查询节点上的插件可用性。当前代码有处理器,但未注册的 payload 不得被视为有效的 gRPC 契约。 |
其中 Config 变更同步的处理器即上文 6.2 节展示的 ConfigChangeClusterSyncRequestHandler.java,其对应测试见 ConfigChangeClusterSyncRequestHandlerTest.java;Distro 传输处理器见 DistroDataRequestHandler.java 及测试 DistroDataRequestHandlerTest.java。
领域规范可以新增更多类别,但必须保留本文档中的调用者、处理器、鉴权、来源与 payload 规则。
8. 请求结果与重试语义
集群 RPC 调用方必须自行定义重试或补偿行为。规范给出的规则:
- 定向调用应显式标识目标成员,并处理目标客户端缺失或已停止的情况;
- 扇出(fan-out)调用应使用当前成员视图,并容忍部分成功;
- 异步调用必须定义回调行为、超时处理与重试调度;
- 超时或传输失败并不能证明远端领域操作没有发生(这是分布式系统的经典"不确定结果"问题);
- 响应成功只代表单一目标节点的处理器级成功,而非全局收敛;
- 后台重试任务必须幂等,或用时间戳、版本、操作类型或领域状态做防护,遵循 Task Execution Spec。
例如:Config 变更同步可以通过其异步 notify 任务路径重试;Naming Distro verify 失败可能发出领域事件并调度修复。本地事件行为由 Event Dispatch And NotifyCenter Spec 定义,具体重试规则归属于 Config 与 Naming 的一致性规范。
9. 边界规则
- 除非接口规范明确声明,内部 RPC不是公共 HTTP、SDK 或开放的 gRPC API;
- 来源标签、连接存在性或成员资格本身都不是授权。内部请求必须使用服务端身份与鉴权过滤器;
- JRaft 成员能力元数据是升级信号,不是身份或授权证明;JRaft 请求准入依赖服务端拦截器校验的凭据;
ClusterRpcClientProxy必须保持传输导向,不得拥有Config、Naming、AI 或插件 payload 的语义;- 领域 payload 不得依赖无界请求体大小、阻塞回调或关键远程线程上的慢速处理器侧远程 IO;
- 影响滚动升级行为的内部 payload 兼容性字段应在领域层面记录;
- 新增集群专用处理器应加入 gRPC API 清单,并链接到拥有其语义契约的领域规范。
10. 相关规范索引
- Foundation Capabilities Spec
- Cluster Membership Spec
- Remote Connection Lifecycle Spec
- Request Filtering And Runtime Context Spec
- AP Consistency Spec
- CP Consistency Spec
- Persistence And Dump Spec
- Task Execution Spec
- Event Dispatch And NotifyCenter Spec
- gRPC API Spec
- Auth And Permission Spec
- Config Spec
- Naming Consistency And Client State Spec
- Control Plugin Spec
关键源码文件速查
- 集群调用入口:core/src/main/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxy.java
- 处理器注册表:core/src/main/java/com/alibaba/nacos/core/remote/RequestHandlerRegistry.java
- 来源限制注解:core/src/main/java/com/alibaba/nacos/core/remote/grpc/InvokeSource.java
- 内网鉴权过滤器:core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java
- JRaft 身份元数据:core/src/main/java/com/alibaba/nacos/core/distributed/raft/auth/JRaftAuthMetadata.java
- 来源常量定义:api/src/main/java/com/alibaba/nacos/api/remote/RemoteConstants.java
- Payload 注册文件:api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload
- 配置变更同步处理器样例:config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandler.java
- Distro 传输处理器样例:naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/DistroDataRequestHandler.java
【免费下载链接】nacosan easy-to-use dynamic service discovery, configuration and service management platform for building AI cloud native applications.项目地址: https://gitcode.com/GitHub_Trending/na/nacos
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考