Nacos Naming 元数据与 Selector 规范深度解析:优先级、保留 Key 与三类 Selector 机制
【免费下载链接】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 中 Naming 模块的元数据体系与 Selector 机制,涵盖 Service / Cluster / Instance 三层元数据的来源、优先级与持久化语义,preserved.*保留 Key 与 Agent Endpoint 投影命名空间,以及内部实例过滤、API 定义的 service selector、客户端NamingSelector三类 selector 的职责边界。读完本文,你将掌握元数据"运维态覆盖运行时态"的判定规则、过期元数据的清理条件,以及如何正确选择和使用 Nacos 的 selector 扩展点。
1. 元数据层次与来源
Naming 元数据分为三个资源层次,分别由不同的管理接口负责读写:
| 层级 | 范围 | 所属接口 |
|---|---|---|
| Service metadata | 服务级发现元数据、保护阈值、遗留 selector 字段和 cluster map。 | Admin API、Console API、Maintainer SDK |
| Cluster metadata | 健康检查器、检查端口行为和 cluster 元数据。 | Admin API、Console API、Maintainer SDK |
| Instance metadata | 实例权重、enabled 状态和扩展元数据。 | 运行时注册和管理 API |
一个重要原则是:元数据不改变 service 身份。也就是说,无论元数据如何增删改,服务的标识(namespace、group、service name)保持不变;元数据变更应当发布 service 或 instance information change 事件,让存储索引、推送和诊断能够及时刷新。本地事件投递机制由事件分发与 NotifyCenter 规范定义,仓库中对应的事件模型位于 InfoChangeEvent.java 与 MetadataEvent.java。
1.1 两类元数据来源与优先级
Naming 区分两类元数据来源,二者在语义、持久化和优先级上有本质区别:
| 来源 | 含义 | 持久化 | 优先级 |
|---|---|---|---|
| 运行时元数据 | 运行时 publisher 在实例注册或心跳时提交的元数据,主要描述由注册进程控制的部署时和运行时状态。 | 绑定到运行时 publisher 及其服务类型。 | 较低 |
| 运维态元数据 | 通过 Nacos 管理路径写入的元数据,例如 Admin API、Console API、Maintainer SDK 或元数据持久化路径,表示运维人员或开发人员的显式意图。 | 由 Nacos 存储,可在运行时 client 消失后按清理规则继续存在。 | 较高 |
优先级规则很明确:当同一个元数据 key 同时存在于运行时元数据和运维态元数据时,对外服务的 Naming 视图必须以运维态元数据为准。运维态元数据优先,因为它代表显式的管理覆盖,且应由 Nacos 持久化或记忆化。
针对不同层级,这一规则的具体体现为:
- service 级元数据:正式 service metadata 本身属于运维态元数据;
- instance 级元数据:运行时注册元数据是基础视图,运维态 instance metadata 会覆盖其同名 key。
2. 保留元数据 Key
绝大多数元数据是用户自定义的 key-value 数据,但 Naming 为核心行为保留了一批 instance metadata key,用户不应随意覆盖这些 key 来改变 Naming 行为:
| Key | 含义 |
|---|---|
preserved.register.source | 实例注册来源。 |
preserved.heart.beat.interval | 心跳间隔覆盖值。 |
preserved.heart.beat.timeout | 心跳 unhealthy 超时覆盖值。 |
preserved.ip.delete.timeout | 心跳删除超时覆盖值。 |
preserved.instance.id.generator | instance id 生成器选择。 |
这些保留 key 直接挂钩核心行为(注册来源、心跳节奏、实例 id 生成),因此规范强调:新的核心行为不得绑定到任意用户元数据 key;如果某个元数据 key 会改变 Naming 行为,它必须被保留并写入文档。
2.1 Agent Runtime Endpoint 投影命名空间
完整的__nacos.agent.endpoint.*__命名空间被保留给 Agent Runtime Endpoint 投影。Version 1 使用以下 key:
| Key | 含义 |
|---|---|
__nacos.agent.endpoint.path__ | URI path。 |
__nacos.agent.endpoint.transport__ | 规范 transport;必须与 Naming cluster 一致。 |
__nacos.agent.endpoint.protocol__ | URI scheme,不是 Agent CallInterface protocol token。 |
__nacos.agent.endpoint.protocolVersion__ | 可选的旧 A2A protocol-version 兼容事实。 |
__nacos.agent.endpoint.supportTls__ | 投影 URI 是否使用 TLS。 |
__nacos.agent.endpoint.query__ | 原始 URI query。 |
__nacos.agent.endpoint.tenant__ | 存在时保存 protocol native tenant。 |
__nacos.agent.endpoint.version__ | 该 Instance contribution 的 runtime Version。 |
__nacos.agent.endpoint.versionRange__ | 该 Instance contribution 的 canonical Version range。 |
__nacos.agent.endpoint.priority__ | Endpoint priority;数值越小优先级越高。 |
该命名空间的使用有严格限制:
- 只有 Agent Endpoint Naming Adapter 可以写入该前缀下的 key,公开 runtime 和 operational metadata 写入必须拒绝这些 key,因此普通的 operational-over-runtime 优先级规则不会覆盖 Agent 投影事实;
- Endpoint 的 weight、enabled 状态和健康状态继续使用 Naming Instance 原生字段,不使用保留 metadata key。
规范还明确了 A2A 与 RAD 两种注册形态的差异:
- 当前按 Version 划分的 A2A 兼容 Layout 可以写入
protocolVersion,新的 RAD 注册不写该值;公开 RAD Endpoint metadata 和 Runtime revision 都排除该值。反向投影旧 A2A 响应时优先使用该值,缺失时回退到目标 Agent CallInterface 的protocolVersion; - 新的无 Version RAD Runtime Naming Instance 只携带一组
version和versionRange,version range 必须是 canonical 形式且必须包含该 runtime Version;Naming metadata 不存储序列化后的 bindings 数组,当前按 Version 划分的 A2A Naming Layout 不受此要求约束。
注册遵循 Naming 完整 batch 覆盖语义:客户端维护同一连接对一个 Agent protocol service 发布的完整 Endpoint batch,在本地移除或替换条目后,通过 Naming batch registration 提交剩余的完整 batch;如果本地计算后的期望 batch 为空,客户端调用整份 publication 注销,不向 Naming 提交空 batch。服务端 Agent adapter 只把提交的 batch 映射为 Naming Instance,不读取和合并该 publisher 的旧 batch。
查询新的 RAD Runtime 时,读取方根据每个 Instance 的 singular pair 构造一个RuntimeVersionBinding,执行 Version range 匹配,并按 Endpoint natural key 聚合查询结果中的bindings[]。RuntimeVersionBinding和bindings[]属于查询投影,不属于 Naming 注册 metadata,精确的 Runtime 投影规则由 Agent 存储规范定义。
3. Selector 分类
Naming 当前存在三类 selector-like 概念,职责边界必须分清:
| 分类 | 范围 | 规范状态 |
|---|---|---|
| 内部实例过滤 | 服务端实现中用于形成发现视图的过滤规则,例如 cluster、enabled、health、保护阈值和内部过滤扩展点。 | 正式 Naming 行为。 |
| API 定义的 service selector | 旧 service API 和 SDK maintainer 方法接受的遗留selector输入。 | 仅兼容保留,待移除。 |
| 客户端 selector | SDK 侧NamingSelector,用于本地 subscribe/unsubscribe 和 listener 匹配。 | 正式 SDK 扩展行为。 |
3.1 内部实例过滤:服务端发现语义
内部实例过滤属于服务端发现语义的一部分,它必须保留其他 Naming 规范定义的 service、cluster、instance、health、enabled、服务类型和保护语义。也就是说,它是"形成发现视图"的过滤层,不能破坏底层的资源模型。
3.2 API 定义的 service selector:遗留兼容
API 定义的 service selector 不应再用于定义新的服务端行为。新的 API 和规范应显式建模过滤条件,或在行为只属于 SDK 本地时使用客户端 selector。它属于仅兼容保留、待移除的状态。
3.3 客户端 selector:SDK 扩展点
客户端 selector 是 SDK 扩展点,它过滤本地 listener 通知或 selection 结果,不得改变服务端的 service、instance、metadata 或一致性状态。这一点在源码中有直接体现:client模块下的 NamingSelectorFactory.java 提供了开箱即用的选择器,例如:
EMPTY_SELECTOR:context -> context::getInstances,不做过滤;HEALTHY_SELECTOR:基于Instance::isHealthy过滤健康实例;- 内部
ClusterSelector:基于 cluster 字符串匹配实例。
NamingSelectorWrapper.java 则负责将 selector 与订阅信息绑定,供本地 subscribe/unsubscribe 与 listener 匹配使用。可以看到,这些实现全部是纯客户端逻辑,通过Predicate<Instance>过滤,与服务端数据无耦合。
4. Cluster 健康检查元数据
Cluster 元数据控制主动健康检查行为,具体包括:
- checker 类型和序列化后的 checker 字段;
- 使用实例端口还是固定检查端口;
- cluster 级扩展元数据。
关键约束是:健康检查元数据属于 cluster,不应复制进 instance identity 或 service identity。
仓库中的 ClusterMetadata.java 完整对应了上述语义,其默认字段值即为规范的实现事实:
| 字段 | 默认值 | 语义 |
|---|---|---|
healthyCheckPort | 80 | 固定检查端口。 |
healthyCheckType | Tcp.TYPE | 默认 checker 类型为 TCP。 |
healthChecker | new Tcp() | 默认健康检查器实例。 |
useInstancePortForCheck | true | 是否使用实例端口做健康检查(默认使用实例端口)。 |
extendData | 空 Map | cluster 级扩展元数据。 |
5. 元数据持久化与过期清理
Service metadata、cluster metadata 和 instance metadata 操作通过CP metadata 路径写入。仓库中的 NamingMetadataOperateService.java 是这条路径的服务端入口,它通过ProtocolManager获取CPProtocol,按Constants.SERVICE_METADATA/Constants.INSTANCE_METADATA分组提交WriteRequest,支持CHANGE(更新 service/instance 元数据)、ADD(为 service 增加 cluster 元数据)、DELETE(删除元数据)等DataOperation,并经cpProtocol.write()写入一致性协议,失败时抛出NacosRuntimeException(SERVER_ERROR)。这印证了规范中"元数据操作走 CP 路径"的定位。
元数据可能在运行时 client 消失后临时存活,过期元数据清理会在所属 service 或 instance 脱离达到配置过期窗口后移除元数据。仓库中的 ExpiredMetadataCleaner.java 实现了这一逻辑:
- 构造时通过
GlobalExecutor.scheduleExpiredClientCleaner以GlobalConfig.getExpiredMetadataCleanInterval()为周期调度; doClean()遍历NamingMetadataManager中的ExpiredMetadataInfo,当currentTime - createTime > GlobalConfig.getExpiredMetadataExpiredTime()时执行清理。
清理还有一个重要的安全前提,规范给出了明确解释:instance metadata 由 instance identity 标识,而非由发布它的 client 标识。client 断开本身并不代表 instance 脱离——同一个 instance 可能已被另一个 client 重新注册。因此清理前必须确认该 instance 已不再注册于其 service;若 instance 仍在注册中,则应保留元数据并停止跟踪该过期记录。
生命周期的最终归属是:
- 运行时元数据跟随运行时 publisher 生命周期;
- 运维态元数据跟随元数据持久化路径,并可以在恢复后覆盖运行时元数据。
6. 待移除问题与演进方向
规范明确列出待移除项:
- API 定义的 service selector 字段和请求参数属于遗留兼容行为。新 API 和 SDK 规范应将其废弃;等兼容要求允许后,应按照兼容与废弃策略规范从正式 Naming 行为中移除。
这提醒使用方:新开发的代码不应再依赖 service API 的selector参数,服务端过滤应显式建模,SDK 本地过滤应使用NamingSelector。
7. 相关规范速览
- Naming 资源规范:service、cluster、instance 资源模型定义;
- Naming 健康检查与保护规范:健康检查与保护阈值语义;
- Naming 一致性与客户端状态规范:一致性保障与客户端状态机;
- Agent 存储规范:Runtime 投影与
RuntimeVersionBinding规则; - 事件分发与 NotifyCenter 规范:元数据变更事件的本地投递;
- 兼容与废弃策略规范:遗留 selector 的废弃与移除路径。
小结
Nacos Naming 的元数据体系可以用三句话概括:三层资源(service / cluster / instance)各归其位,两类来源(运行时 / 运维态)运维优先,一类保留 Key(preserved.*与__nacos.agent.endpoint.*__)不可侵犯;而 selector 机制则严格划分为服务端内部过滤、遗留 API selector 与客户端NamingSelector三类,各自只在自己的边界内生效。理解并遵循这些规则,是在生产环境中正确使用 Nacos 元数据能力、避免与 Agent Endpoint 等新特性冲突的前提。
【免费下载链接】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),仅供参考