Nacos 中的 Istio Mesh Configuration Protocol(MCP)详解:订阅式配置分发协议与源码实现
【免费下载链接】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
MCP(Mesh Configuration Protocol)是 Istio 生态中用于在网格配置管理组件与数据面组件之间分发配置的订阅式协议,其设计源于 Envoy xDS,但服务与消息定义自成体系。本文以本仓库istio模块中保留的 MCP 协议文档与 proto 定义为主体,完整讲解 MCP 的 Source/Sink 模型、集合与元数据数据模型、基于 nonce 的 ACK/NACK 配置更新流程,并结合 Nacos 对ResourceSource服务的真实实现(NacosMcpService、McpConnection、ServiceEntryMcpGenerator等)剖析其落地原理。读完本文,你将能理解 MCP 的完整消息交换语义,并在阅读或二次开发 Nacos 的 Istio 适配模块时快速定位协议与实现的对应关系。
一、MCP 是什么:从 xDS 到订阅式配置分发
本仓库的 istio/src/main/resources/proto/mcp/Readme.md 是 MCP 协议的权威说明文档,配套的三个 proto 文件位于同目录的v1alpha1子目录下:
- mcp.proto:定义节点标识、请求/响应消息与
ResourceSource/ResourceSink服务; - metadata.proto:定义所有 MCP 资源必须携带的公共元数据;
- resource.proto:定义协议传输层的资源封装
Resource。
MCP 基于 Envoy xDS 协议 的流式 gRPC 订阅思想,但与 xDS 在具体服务与 proto 定义上并不相同,两者只保持"概念对齐"。它的核心定位是:为配置消费者(sink)提供一种订阅式获取配置集合(collection)的通道。
MCP 的基本工作方式可以概括为订阅-推送-确认三段式:
- 配置消费者(sink)向配置生产者(source)发起订阅请求,声明自己关注哪些资源集合;
- source 在资源发生新增、更新或删除时,向 sink 推送资源更新;
- sink 处理成功后发送 ACK(确认),处理失败(如资源非法、无法解码)则发送 NACK(拒绝)。
协议约定 source 在同一集合上同一时刻只能有一个在途(outstanding)更新,必须等待上一次更新的 ACK/NACK 后才能推送下一个更新,这保证了配置变更的有序性。
二、核心模型:Source 与 Sink 的双向流式 gRPC 服务
MCP 由一对双向流式 gRPC 服务构成:ResourceSource与ResourceSink。二者在消息交换语义上完全等价,唯一实质区别是谁发起连接、谁打开 gRPC 流。
2.1 ResourceSource:sink 作为客户端拨号
当资源的生产者在服务端、消费者在客户端时,使用ResourceSource服务。文档明确说明:Galley 默认实现ResourceSource服务,Pilot/Mixer 作为客户端接入。其 proto 定义为(见 mcp.proto):
// Service where the sink is the gRPC client. The sink is responsible for // initiating connections and opening streams. service ResourceSource { // The sink, acting as gRPC client, establishes a new resource stream // with the source. The sink sends RequestResources message to // and receives Resources messages from the source. rpc EstablishResourceStream(stream RequestResources) returns (stream Resources) {} }流程为:sink(客户端)拨号到 source(服务端)并建立新的 gRPC 流,随后 sink 发送RequestResources,source 回送Resources。
2.2 ResourceSink:source 作为客户端拨号
当资源的生产者是客户端、消费者是服务端时,使用ResourceSink服务。典型的场景是:Pilot 位于另一个集群,无法作为客户端主动连回 Galley,此时由 Galley "拨出"(dial-out)到远程的配置 sink。此时 Pilot 实现ResourceSink服务,Galley 作为客户端连接。其 proto 定义为(见 mcp.proto):
// Service where the source is the gRPC client. The source is responsible for // initiating connections and opening streams. service ResourceSink { // The source, acting as gRPC client, establishes a new resource stream // with the sink. The sink sends RequestResources message to and // receives Resources messages from the source. rpc EstablishResourceStream(stream Resources) returns (stream RequestResources) {} }注意这里的流方向与ResourceSource相反:source 作为客户端拨号建立流,sink 发送RequestResources,source 回送Resources。也就是说,"谁发送RequestResources、谁接收Resources"始终由协议角色决定,与谁主动建连无关。
三、数据模型:集合(Collections)与公共元数据(Metadata)
MCP 只是"传输机制",它定义了一套通用的、按资源粒度划分的元数据格式;而资源的具体内容(如 VirtualService、DestinationRule)由外部 API 另行定义。整个数据模型分三层:集合 → 资源 → 元数据。
3.1 集合(Collection)命名规范
同类型的资源被组织进具名集合。Istio API 的集合名遵循istio/<area>/<version>/<api>形式,其中<area>、<version>、<api>由 API 风格指南定义。例如 VirtualService 的集合名为:
istio/networking/v1alpha3/virtualservices在本仓库的 Nacos 实现中,ApiConstants明确给出了集合名的常量定义(见 ApiConstants.java):
public static final String MCP_PREFIX = "istio/"; public static final String SERVICE_ENTRY_COLLECTION = MCP_PREFIX + "networking/v1alpha3/serviceentries";即 Nacos 目前通过 MCP 暴露的集合是istio/networking/v1alpha3/serviceentries(服务条目 ServiceEntry),与协议文档给出的命名范式完全一致。源码中还有注释表明当前仅支持 ServiceEntry 这一种 Istio CRD(见 ApiConstants.java)。
3.2 资源封装(Resource)
协议传输层用统一的Resource消息包裹任何类型的资源(见 resource.proto):
// Resource as transferred via the Mesh Configuration Protocol. Each // resource is made up of common metadata, and a type-specific resource payload. message Resource { // Common metadata describing the resource. istio.mcp.v1alpha1.Metadata metadata = 1; // The primary payload for the resource. google.protobuf.Any body = 2; }metadata:公共元数据,描述资源本身;body:google.protobuf.Any类型的具体资源负载,通过type_url标明资源类型。
在 Nacos 的ServiceEntryMcpGenerator中可以看到这一封装的实际构造(见 ServiceEntryMcpGenerator.java):它将 ServiceEntry 序列化进Any,type_url使用type.googleapis.com/istio.networking.v1alpha3.ServiceEntry(常量见 ApiConstants.java),再与元数据一起组装成Resource。
3.3 公共元数据(Metadata)
所有 MCP 资源必须携带Metadata消息(见 metadata.proto),字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 资源的全限定名,在集合内唯一。由目录(directory)+ 基名(basename)组成,段之间以/分隔,各段必须是合法 DNS 标签。右端段为基名,左端各段表示资源层级(类似反向 DNS)。Kubernetes 上命名空间资源形如<k8s namespace>/<k8s resource name>,集群级资源位于层级根部,形如/<k8s resource name> |
create_time | Timestamp | 资源创建时间戳 |
version | string | 资源版本号,用于判断资源是否在更新中发生变化,sink 应将其视为不透明值 |
labels | map<string,string> | 标签,用于在集合内组织和分类资源 |
annotations | map<string,string> | 注解,供 source 与 sink 之间传递任意附加元数据 |
需要说明的是,原 Readme 的 "Metadata" 小节本身只有标题没有正文,但同目录 metadata.proto 给出了完整字段定义,上表即来自该文件,可作为元数据语义的权威依据。
四、连接建立(Connection Establishment)
根据协议文档,连接建立分两种角色场景:
ResourceSource服务:sink 作为 gRPC 客户端拨号到服务器,建立新的 gRPC 流,然后发送RequestResources并接收Resources消息;ResourceSink服务:source 作为 gRPC 客户端拨号到服务器,建立新的 gRPC 流,sink 发送RequestResources并接收Resources消息。
连接建立后,协议进入配置更新阶段。在 Nacos 的 gRPC 服务装配中,IstioServer在启动时把NacosMcpService(实现了ResourceSource服务端)与NacosXdsService一并注册进 gRPC Server(见 IstioServer.java):
server = ServerBuilder.forPort(istioConfig.getServerPort()) .addService(ServerInterceptors.intercept(nacosMcpService, serverInterceptor)) .addService(ServerInterceptors.intercept(nacosXdsService, serverInterceptor)) .build();也就是说,Nacos 在 Istio 集成中扮演的是ResourceSource(配置生产者/服务端)角色,等待 Pilot 等 sink 客户端拨入订阅。
五、配置更新协议:RequestResources / Resources 与 nonce 机制
配置更新协议源自Incremental xDS,协议交换大体一致,只是移除了资源提示(resource hints)。以下流程对ResourceSink与ResourceSource两个服务均适用。
5.1 基本规则
- 资源先按集合组织;集合内资源通过元数据
name唯一标识;同一命名资源的多个版本通过资源版本号区分新旧。 RequestResources消息在两种场景下发送:- MCP 双向变更流的首条消息(初始订阅);
- 对前一条
Resources消息的 ACK 或 NACK 响应——此时response_nonce被设置为Resources消息中的 nonce 值,ACK/NACK 通过后续请求中是否携带error_detail来区分。
- nonce 字段用于按集合配对
RequestResources与Resources消息。source 在同一集合上同一时刻只应有一个在途的Resources消息,并等待 sink 的 ACK/NACK。 - sink 在解码、校验并持久化更新到内部配置存储后,应尽快回送 ACK/NACK。
- source 应忽略携带过期或未知 nonce(与最近发送的
Resources消息 nonce 不匹配)的请求。
RequestResources消息的完整字段(见 mcp.proto):
| 字段 | 说明 |
|---|---|
sink_node | 发起请求的 sink 节点标识(SinkNode,含id与annotations) |
collection | 请求的资源集合名,如istio/networking/v1alpha3/virtualservices、k8s/<apiVersion>/<kind> |
initial_resource_versions | 仅当RequestResources是流中首条消息时必须填充;key 为 sink 已知资源的名称,value 为对应资源的版本信息 |
response_nonce | 当该请求是对前一条Resources的 ACK/NACK 时,必须携带Resources中的 nonce;否则省略 |
error_detail | 当先前接收的资源无法应用时填充,其message字段提供与失败相关的源端内部错误 |
incremental | 请求对指定集合进行增量更新;source 可以选择响应增量更新,也可以忽略该请求而返回全量更新 |
其中 ACK/NACK 的判定规则在 proto 注释中写得很明确(见 mcp.proto):
// * ACK (nonce!="",error_details==nil) // * NACK (nonce!="",error_details!=nil) // * New/Update request (nonce=="",error_details ignored)Resources消息的字段(见 mcp.proto):
| 字段 | 说明 |
|---|---|
system_version_info | 响应数据的版本(仅用于调试) |
collection | 资源所属集合名 |
resources | 以公共Resource消息包装的响应资源。incremental=true时为待增/更新的资源数组(修改 sink 现有集合);incremental=false时为该集合的完整资源集(替换先前推送的全部资源) |
removed_resources | 已删除、需从 sink 移除的资源名列表(对不存在的资源可忽略)。incremental=true时表示从集合中删除;incremental=false时忽略该字段 |
nonce | 必填,用于将Resources与后续RequestResources的 ACK/NACK 唯一配对 |
incremental | 本次资源响应是否为增量更新,source 只有在 sink 请求增量时才应发送增量 |
5.2 全量更新与增量更新
协议文档强调,sink 可以在RequestResources中请求增量更新,但能否真正增量取决于 source 是否支持:
- 当 source 不支持增量时,推送的
Resources中incremental恒为false,无论 sink 是否请求增量; - 任何时候 source 都可以决定推送全量状态更新,忽略 sink 的增量请求;
- 一次更新要真正以增量方式发送,双方必须在每次请求/响应上协商一致(即都同意使用增量)。
5.3 成功示例:全量更新与增量更新
全量更新成功流程:sink 收到一系列变更并逐一 ACK——sink 发送初始RequestResources(携带集合、sink 节点标识、nonce 字段与initial_resource_version);source 在资源就绪后回送Resources;sink 处理后发送新的RequestResources,携带上次成功应用的版本与 source 提供的 nonce,表示 ACK。如此往复,形成"请求→推送→确认→再请求"的循环。
增量更新成功流程:在 source 支持增量的前提下,同样的期望资源可以按增量方式交付——source 只推送与 sink 当前状态之间的差异(新增/更新的资源 + 删除的资源名),并在Resources.incremental=true中标识。
5.4 错误示例与 NACK 语义
当某次变更无法应用时,sink 会回送 NACK(RequestResources中携带response_nonce与error_detail)。协议文档特别强调:
- sink只应在异常情况下 NACK,例如一批资源非法、格式错误或无法解码;
- NACK 的更新应触发告警,供后续人工排查;
- source不应重发先前已被 NACK 的同一批资源;
- 也可以先把更新**灰度推送(canary push)**到专门的 sink 上验证正确性(不产生 NACK),再推送给更大规模的 sink 集群。
5.5 断线重连与初始资源版本
nonce 用于匹配RequestResources与Resources。重连时,sink 可以为每个集合指定initial_resource_version(携带已知资源版本),尝试与同一 source 恢复会话,从而避免全量重新同步。
六、Nacos 对 MCP 的落地实现:从协议到代码
Nacos 的istio模块把上述协议文档真正实现为了可运行的 gRPC 服务。下面沿着源码链路说明协议语义在 Nacos 中的一一对应关系。
6.1 NacosMcpService:ResourceSource 服务端实现
NacosMcpService.java 继承ResourceSourceGrpc.ResourceSourceImplBase,是协议中ResourceSource服务在 Nacos 侧的实现,即 Nacos 作为配置 source 对外提供istio/networking/v1alpha3/serviceentries集合的订阅能力。
建立流时(establishResourceStream),Nacos 会先初始化服务信息快照,为每个 sink 连接创建一个McpConnection并登记到连接表中(见 NacosMcpService.java)。收到RequestResources后调用process方法决定是否推送:
- 若请求携带
error_detail(code != 0),按 NACK 处理并记录错误日志,不推送(见 NacosMcpService.java); - 若
response_nonce为空,视为初始订阅请求,为该连接建立该集合的WatchedStatus并推送(见 NacosMcpService.java); - 若
watchedStatus为 null,视为重连请求,重新建立订阅并推送(见 NacosMcpService.java); - 若请求携带的
response_nonce与最近一次推送的 nonce 不匹配,判定为过期请求,直接忽略(见 NacosMcpService.java); - 若 nonce 匹配,则视为对该更新的ACK,记录 acked nonce(见 NacosMcpService.java)。
这段逻辑与协议文档中"source 应忽略 stale/unknown nonce"、"ACK 通过回带 nonce 完成"的约定完全对应。
6.2 McpConnection 与连接生命周期
McpConnection.java 继承 AbstractConnection.java。AbstractConnection维护连接的完整生命周期:
- 以"客户端 id + 自增序号"生成连接 id(
clientId + "-" + id,见 AbstractConnection.java); - 用
Map<String, WatchedStatus>按资源类型记录每个集合的订阅状态(见 AbstractConnection.java)。
McpConnection.push在向 sink 发送Resources后,同步更新WatchedStatus中的最新版本与最新 nonce(见 McpConnection.java),为下一次 ACK/NACK 比对提供依据。
6.3 ServiceEntryMcpGenerator:从 Nacos 服务信息到 MCP 资源
ServiceEntryMcpGenerator.java 实现ApiGenerator<Resource>,负责把 Nacos 的服务信息快照转换为 MCPResource列表:遍历服务信息映射,为每个服务构建ServiceEntryWrapper(ServiceEntry + Metadata),再把 ServiceEntry 包装进Any(type_url为type.googleapis.com/istio.networking.v1alpha3.ServiceEntry),最终组装成Resource。这正是协议数据模型中"公共元数据 + 类型化负载"的实际生产代码。
NacosMcpService.buildMcpResourcesResponse则负责构造整个Resources响应:设置集合名、追加资源列表、写入快照版本作为system_version_info,并用NonceGenerator生成新的 nonce(见 NacosMcpService.java)。
6.4 EventProcessor:事件驱动的主动推送
配置更新不只是响应式推送,Nacos 服务信息发生变化时还需主动向已订阅的 sink 推送。EventProcessor.java 用容量为 20 的阻塞队列承接PushRequest事件,后台消费者线程以 100ms 为轮询窗口做去抖合并(同一窗口内的多个事件只触发一次处理),然后异步生成资源快照并依次调用nacosXdsService.handleEvent、nacosXdsService.handleDeltaEvent与nacosMcpService.handleEvent(见 EventProcessor.java)。
NacosMcpService.handleEvent会为每个已建立连接的 sink,按其订阅的SERVICE_ENTRY_COLLECTION集合推送最新Resources(见 NacosMcpService.java)。这完整还原了协议中"source 在资源新增/更新/删除时推送更新"的行为。
七、其他相关消息:MeshConfig 与聚合服务
除ResourceSource/ResourceSink之外,mcp.proto 还定义了一套非增量的 MeshConfig 消息与聚合服务:
MeshConfigRequest:请求一组同类型、带版本号的资源,携带version_info、sink_node、type_url、response_nonce、error_detail;MeshConfigResponse:回送version_info、resources、type_url、nonce;IncrementalMeshConfigRequest/IncrementalMeshConfigResponse:增量版本的请求/响应,支持按资源粒度跟踪状态与removed_resources删除列表;AggregatedMeshConfigService:通过单条 gRPC 流按type_url多路复用多个资源类型的更新序列,并提供StreamAggregatedResources与IncrementalAggregatedResources两个 RPC,用于支撑大规模 MCP 资源场景。
此外SinkNode(id+annotations)用于标识 MCP sink 节点实例,source 可借此区分不同 sink 的差异化配置;其权威身份仍应来自底层传输层(如 RPC 凭证),节点标识本身不具备权威性。
八、协议要点速查与小结
| 维度 | 关键约定 |
|---|---|
| 服务对 | ResourceSource(sink 拨号)/ResourceSink(source 拨号),消息交换语义等价 |
| 集合命名 | istio/<area>/<version>/<api>,如istio/networking/v1alpha3/serviceentries |
| 资源封装 | Resource=Metadata+google.protobuf.Any body |
| 元数据 | name(集合内唯一全限定名)、create_time、version(不透明)、labels、annotations |
| 请求时机 | 流首条消息(初始订阅)或对Resources的 ACK/NACK 响应 |
| ACK/NACK | 回带response_nonce且无error_detail为 ACK;回带response_nonce且有error_detail为 NACK |
| 在途限制 | source 每集合同一时刻仅允许一个在途更新,等待 ACK/NACK 后再推送 |
| nonce | 按集合配对请求与响应;过期/未知 nonce 的请求应被忽略 |
| 增量更新 | 需 source 支持且双方逐次协商;source 可随时退回全量更新 |
| NACK 处置 | 仅异常时使用,触发告警;source 不重发已 NACK 的资源,可灰度推送验证 |
MCP 协议为服务网格中的配置分发提供了干净、有序、可确认的订阅通道。在 Nacos 中,istio模块通过 NacosMcpService.java 以ResourceSource服务端角色对外开放istio/networking/v1alpha3/serviceentries集合,将 Nacos 的服务发现数据转换为 Istio 的 ServiceEntry 资源并推送给 Pilot 等订阅方。协议文档(Readme.md)、proto 定义(mcp.proto、metadata.proto、resource.proto)与上述实现代码三者相互印证:nonce 配对、ACK/NACK 判定、连接生命周期、事件驱动的主动推送等协议语义,均能在 Nacos 源码中找到一一对应的实现,这也为读者在 Nacos 上扩展更多 Istio CRD 集合(如 VirtualService、DestinationRule)提供了清晰的扩展点——只需参照ApiConstants增加集合常量,并实现对应的ApiGenerator即可。
【免费下载链接】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),仅供参考