Repository navigation
Discussion 3342
- 编号:#3342
- 分类:General
- 状态:已关闭 / 无已采纳答案
- 创建:2026-05-24T05:48:22Z
- 更新:2026-06-25T09:08:40Z
- 作者:AlexStocks
社区原始记录;其中的建议、未决提案和版本描述需结合 版本与升级 核对。快照日期:2026-09-20。
阅读说明:原文引用作者本地磁盘文件的链接已转为文字路径,避免产生无效 Wiki 导航;原始内容完整保存在 sources/discussions.json。
审查日期: 2026-05-24
审查基准: dubbo-go-metadata-design-spec-zh.md (61,504 字符, 20 章节)
对照参考: Java Dubbo 3.3 源码 (MetadataServiceV2 proto, MetadataInfo, ServiceInstanceHostPortCustomizer)
这是一份高质量的设计文档。结构清晰、问题识别准确、实施路径合理。作者对 Java Dubbo 和 dubbo-go 的差异有深入理解,并且明确区分了"协议兼容性"和"实现风格"的边界。
但仍存在多个 CRITICAL 级别和 WARNING 级别的问题需要在实施前解决。
综合评分: ★★★☆ (3.5/5) — 方向正确,细节需补强
Java Dubbo 的 ServiceInfo key 格式为 {group}/{interface}:{version}:{protocol}。
方案中提到了 key 格式但未明确规定分隔符和字段顺序。
dubbo-go 当前已有 common.ServiceKey(intf, group, version) 和 common.MatchKey(serviceKey, protocol),生成逻辑接近 Java,但方案没有把这个作为 wire contract 写死。
风险: 如果 Go 使用不同分隔符或字段顺序,Java/Go 之间的 revision 对比永远不匹配,导致消费者持续重新获取元数据。
建议: 在方案中明确定义 key 格式,包括:
- 分隔符 (
/,:) - 空值处理:group 为空时省略前缀;version 为空或等于
0.0.0时省略版本段 - 字段顺序:group → interface → version → protocol
关键实现细节: 当前 common.MatchKey(serviceKey, protocol) 无条件追加 ":" + protocol,当 protocol 为空时会生成尾随冒号(如 "group/interface:version:")。Java 的行为是 protocol 非空才追加。设计文档必须明确 Go 实现需与 Java 对齐:protocol 为空时不追加冒号,否则空 protocol 边界场景下的 revision 会与 Java 不一致。
Java 使用 TreeMap 保证迭代顺序确定性。Go 的 map[string]*ServiceInfo 迭代顺序是完全随机的。
方案第 8.2 节已正确处理了两层排序:外层 sort.Strings(keys) 排序 service keys,ToDescString() 内部也使用了 sortedParamsString(s.Params) 处理 params map 随机迭代问题,方向是正确的。
实现验收约束: 排序要求不止一层。Java 使用 TreeMap / TreeSet 保证所有层级确定,Go 实现必须覆盖所有 map 层级,包括 Services、Params、method-level params、exported/subscribed URL sets。
建议: 以下测试用例必须全部覆盖:
-
TestRevision_Deterministic_ServiceInsertionOrder:相同服务不同插入顺序 → 相同 revision -
TestRevision_Deterministic_ParamInsertionOrder:params key/value 顺序不同 → 相同 revision -
TestRevision_Deterministic_MethodParamInsertionOrder:method-level params 顺序不同 → 相同 revision -
TestRevision_JavaGoldenVectors:同一 metadata,Go/Java revision 完全一致
Java RevisionResolver.calRevision() 使用 MD5。方案中说用 revision.Resolve() 但未指明具体算法。
风险: 如果 Go 用 SHA-256 而 Java 用 MD5,相同元数据会产生不同 revision,导致消费者误判元数据变更。
建议: 这是互操作契约,不是实现偏好,方案中应明确写"必须 MD5"而不是"建议 MD5"。完整规格:
revision.Resolve 必须等价于 Java Dubbo RevisionResolver.calRevision:
- 输入:app + sorted ServiceInfo.toDescString()
- 算法:MD5
- 输出格式:与 Java MD5Utils.getMd5(metadata) 完全一致
- 空 services:EMPTY_REVISION = "0"(见 C-6)
添加跨语言 revision 一致性测试(golden vector 测试)。
Java synchronized 天然保护了所有访问。Go 的 RWMutex 如果只保护 GetMetadataInfo() 调用,但返回 *MetadataInfo 指针,调用方可以在锁外读写 Services map。
风险: Go map 并发读写会 panic。即使有 RWMutex,返回的指针被并发访问仍然崩溃。
设计文档虽然要求 MetadataManager 加 RWMutex,也要求 Clone() 深拷贝,但同时保留了 GetMetadataInfo(registryId string) *info.MetadataInfo 这个返回 live pointer 的 API。Manager 的锁只能保护 map lookup,不能保护返回对象后的访问。
建议:
- 明确 API 语义分层:
-
GetMetadataInfoSnapshot(registryID string) *info.MetadataInfo:返回 deep clone,用于发布/cache/consumer 读取 -
MutateMetadataInfo(registryID string, fn func(*info.MetadataInfo) error) error:受锁保护的写路径 - 原
GetMetadataInfo如需保留兼容,注明"仅用于兼容旧调用,不应用于新写路径"
-
-
Clone()方法必须深度复制所有 map(Services、exported URLs、subscribed URLs) -
MetadataInfo.Services不应直接暴露可变 map;getters 返回 copy 或只读视图
方案将 cache key 从 {revision} 改为 {app}:{revision},这是正确的设计,但方案未明确持久化 cache 文件的版本化或清理策略。
当前代码确认:revisionToMetadata 是 map[string]*info.MetadataInfo(key 只有 revision);metaCache.Get(revision) 也只用 revision;cacheOnce 用第一个 app 初始化 cache manager。
风险: 如果 dubbo-go 存在 file-based 本地持久化 cache,key 格式变更后旧格式条目可能被新代码误读,导致错误 metadata 复用。进程内内存 cache miss 是正常的升级行为,不是核心风险。
建议:
- 明确持久化 cache 文件是否存在;如果存在,key 格式变更时需清理旧格式文件或给 storage format 加版本号(方案 §8.2 已提到"旧 revision cache 和新 revision cache 不能混用",但未说明具体处理方式)
-
cacheOnce用第一个 app 初始化的问题需要修复:改为 app-neutral 共享 cache 或 per-provider-app cache manager
方案提出单个 MetadataManager 用一把 RWMutex 保护所有 registry 的 MetadataInfo。
问题: 如果 calRevision() 持写锁计算哈希(可能遍历大量服务),会阻塞所有并发读取。多 registry 场景下,一个 registry 的 revision 变更会阻塞其他 registry 的读取。
建议: 考虑按 registryId 分片加锁,或使用 MetadataInfo 级别的 mutex。
Java 在 synchronized 块内读写 updated。Go 如果将 updated 作为普通 bool 字段,在锁外读取可能读到脏值。
建议: 使用 atomic.Bool 或确保所有 updated 访问都在锁内。
Java MetadataInfo 有 extendParams(扩展参数)和 instanceParams(实例参数)两个独立 map。
设计文档 §8.2 已写"instance-level params 和 service-level revision params 保持分离",方向正确,但未明确这两个 map 在 Go 侧是否需要字段级等价模型。
风险: 如果 Go 将两者合并,可能丢失 Java wire format 里的 params 分层语义,影响互操作。
建议: 方案应补充说明:
- Go 侧是否需要与 Java
extendParams/instanceParams字段语义对等 - 这两个 map 是否参与 JSON wire format
- 是否仅作为运行时派生字段,不序列化
设计文档 §11.4 已写了主地址选择原则(preferred business protocol → 第一个 exported service URL → metadata-service port 兜底),方向正确,但 preferred/default business protocol 的配置来源、协议优先级顺序和 same-protocol multi-port 的 tie-breaker 仍未量化。
风险: "第一个 exported service URL"在 Go map 或并发 export 场景里可能不稳定,导致 primary address 非确定性。
建议: 将选择规则写成可测试的确定性规格:
- 显式配置的 preferred protocol(来自
dubbo.application.preferred-protocol或同等配置) - application/default protocol
- stable sorted business endpoints 中的第一个(排序键:
(protocol priority, host, port, service key)) - 仅当没有 business endpoint 时,使用 metadata-service endpoint
明确协议优先级顺序(如 tri > dubbo > rest),否则 step 3 的 sort key 无法确定。
Java 的 synchronized 天然串行化 revision 计算。Go 的 RWMutex 如果处理不当,50 个服务同时 export 可能触发 50 次 revision 计算。
设计文档的 PR 5 已将 revision 计算放到 CalAndGetRevision(),并用 updated 标志避免未变化时重复计算,这已经是基础保护。
建议: 第一阶段不必引入 debounce,以免改变注册时序。应先保证 dirty 标志正确和注册时单点计算;如果 benchmark 显示大量服务 export 时 revision 计算成为瓶颈,再引入批量/防抖机制作为后续优化。
方案第 14 节讨论了 consumer 侧的错误处理,但 provider 侧 MetadataService export failure 的行为策略不够明确。Go 服务端不应在设计文档里承诺 goroutine 自动重启,除非已有 supervisor 模型。
建议: 方案应补充 provider 侧的 fail-safe 规则:
-
local模式下,若计划导出 V1 失败,应返回启动错误,或至少不注册storage-type=local的 instance -
metadata-service-protocol=tri且 V2 导出失败但 V1 成功,capability 只能声明 V1,写meta-v=1.0.0 - V1/V2 全部失败,不能注册
dubbo.metadata.storage-type=local的 provider instance -
remote模式下,本地 MetadataService export failure 不影响启动
MetadataInfo 包含所有服务的完整描述。100+ 服务 x 1000+ 方法 = MB 级元数据。此项不应阻塞 PR 1-3,但需要在方案中有明确的阈值策略。
建议: 第一阶段增加 metadata size warning 阈值,而非实现压缩:
- metadata JSON size > 1 MiB 时 warning
-
4 MiB 时可配置 fail-fast 或跳过 remote publish
具体阈值需结合 Nacos/ZK/etcd backend 的 value size 限制评估,适合在 PR 5/6 阶段补充。
Java 对 null 处理较宽容(空字符串、空 map)。Go 读 nil map 不 panic,但写 nil map 会 panic,风险主要在 AddService / mutation 路径和外部反序列化后的对象初始化。
建议:
- 所有 MetadataInfo/ServiceInfo 构造函数必须初始化 map 字段
- 以下测试用例必须覆盖:
TestMetadataInfo_ZeroValueSafeReadTestMetadataInfo_DeserializedNilServices_AddServiceTestServiceInfo_NilParams_ToDescStringTestServiceInfo_NilParams_GetMethods
Java Dubbo 3.3 RevisionResolver.EMPTY_REVISION 的值是 "0",与设计文档 §8.2 的 revision="0" 一致,值已对齐。
文档缺口: 方案未显式引用该常量名,读者可能误以为 "0" 是 dubbo-go 自定义值,或者在实现时随意改为其他哨兵值。
建议: 在设计文档中补充:
空 services 时 revision 返回 RevisionResolver.EMPTY_REVISION,即 "0",与 Java Dubbo 3.3 一致。
并在代码中定义对应常量,不要直接使用字面量 "0"。
当前 dubbo-go common.MatchKey(serviceKey, protocol) 无条件追加 ":" + protocol,当 protocol 为空时会生成尾随冒号(如 "group/interface:version:")。Java 的行为是 protocol 非空才追加冒号。
风险: 边界场景下(protocol 字段为空字符串),Go 生成的 match key 与 Java 不一致,导致 revision 计算偏差。这是 C-1 里"字节级对齐"的具体实现要求,需要单独列出以确保实现时不被忽略。
建议: 修改 MatchKey 实现,protocol 为空时不追加 ":",并增加对应边界 case 测试。
| 维度 | 评分 | 说明 |
|---|---|---|
| 单元测试覆盖 | ★★★ | 基本覆盖核心组件,但缺少并发/边界场景 |
| 集成测试覆盖 | ★★ | 涵盖本地/远程元数据、Java 兼容性,但场景不足 |
| 兼容性测试 | ★★ | 有多协议/多注册中心覆盖,但缺少升级/降级路径 |
| 验收标准可测性 | ★★ | 24 条标准中有模糊项,部分缺少量化指标 |
| 回归保护 | ★ | 仅提及多提供者 URL 生成,覆盖面严重不足 |
综合评分: ★★ (2/5) — 框架存在但存在显著缺口
| # | 测试项 | 风险 |
|---|---|---|
| 1 |
MetadataManager 并发安全 (go test -race,100 goroutines 并发 Add/Remove/Clone/CalAndGetRevision) |
数据竞争 → panic |
| 2 | 部分 export 失败场景 (V1 成功 V2 失败、V2 成功但 capability 未写入、remote publish 失败) | meta-v 与实际能力不符 |
| 3 | nil/空 metadata 不 panic、不写 cache | consumer metadata fetch crash |
| 4 | Java golden vector:同一 metadata,Go/Java revision 完全一致 | Java/Go 互操作 revision 永远不匹配 |
| 5 | Multi-app same revision cache 隔离:appA:0 与 appB:0 不串 | 跨应用 metadata 错乱 |
| # | 测试项 | 风险 |
|---|---|---|
| 6 | 资源泄漏检测 (goroutine/memory/connection,使用 goleak) | 长期运行 OOM |
| 7 | 缓存 TTL/淘汰策略 | 内存泄漏、过期数据 |
| 8 | 多注册中心完整场景 (故障转移、数据一致性) | 生产部署故障 |
| 9 | 性能基准测试 + CI benchstat 回归 | 无性能 SLA |
| 10 | 升级/降级兼容性测试 (滚动升级、版本共存) | 升级中断服务 |
- 灰度发布场景
- 服务治理联动 (路由/限流/熔断)
- 配置热更新
- 1000+ 服务实例压力测试
- 接口级↔应用级动态切换
- 网络分区与 toxiproxy 超时恢复
| 基础设施 | 用途 |
|---|---|
| 嵌入式 Nacos / testcontainers-go | 集成测试注册中心 |
| Mock Metadata Report | 单元测试 + 故障注入 |
| Java Dubbo Docker 容器 | 互操作性双向测试 |
| toxiproxy | 网络故障注入 |
| 独立 benchmark CI runner | 性能回归检测 |
| 项 | Java Dubbo 3.3 | 方案 | 状态 |
|---|---|---|---|
| MetadataServiceV2 proto | GetMetadataInfo + GetOpenAPIInfo | 对齐 (PR2) | ✓ |
| ServiceInfoV2.Port | int32 port 字段存在 | 保留 Port 字段 | ✓ |
| Revision 计算 | app + sorted services + toDescString | 方案第 8.2 节 | ✓ (方向正确) |
| calAndGetRevision | synchronized + updated 标志 | 方案第 8.2 节 | ✓ (概念对齐) |
| EMPTY_REVISION | Java RevisionResolver.EMPTY_REVISION = "0"
|
方案用 "0",值已对齐;需补充常量名引用 |
✓(需澄清命名) |
| ServiceInstance 主地址 | preferred protocol endpoint | 方案第 11.4 节 | ✓ (方向正确) |
| 日志格式 | [METADATA_REGISTER] 前缀 | 方案第 15 节 | ✓ |
| 项 | Java Dubbo 3.3 | 方案 | 状态 |
|---|---|---|---|
| ServiceInfo key 格式 |
{group}/{interface}:{version}:{protocol},空值省略 |
未明确分隔符和空值规则 | |
MatchKey 空 protocol |
protocol 为空时不追加冒号 | 当前 Go 实现无条件追加 | |
| extendParams/instanceParams | 两个独立 map | 未说明 Go 侧是否需要字段级等价 | |
| MetadataParamsFilter | SPI 过滤运行时参数 | 提到轻量过滤但未定义 SPI | |
| EMPTY 常量命名 | RevisionResolver.EMPTY_REVISION |
值 "0" 已对齐,但未引用常量名 |
|
| rawMetadataInfo | JSON 缓存与 revision 绑定 | 未提及 |
- Go-first 约束清晰 — 明确拒绝照搬 Java 架构 (ApplicationModel/ConfigManager),保持 Go 的包结构
- Strategy + Capability 分离 — "计划导出什么" vs "实际导出了什么" 分离,这是修复 meta-v 问题的关键
- meta-v 硬规则 — "meta-v=2.0.0 only if V2 actually exported" 是正确的核心原则
- 6 个 PR 的分步策略 — 每个 PR 范围可控,风险隔离好
- 双模型审查意识 — 方案本身就体现了区分 wire contract 和实现的理念
- 错误处理原则 — 第 14 节明确了 "metadata 获取失败不 panic" 的原则
- 24 条验收标准 — 虽然部分需要量化,但覆盖面是充分的
- 明确 ServiceInfo key/match key 格式,包括空值规则和
common.MatchKey空 protocol 修复 (C-1, W-10) - 明确 revision 哈希算法必须 MD5,补充完整计算规格 (C-3)
- 补充 MetadataInfo 并发安全 API 语义分层,明确哪些路径必须用 snapshot (C-4)
- 明确持久化 cache key/format 迁移策略,
cacheOnce作用域修复 (C-5) - 补充 EMPTY_REVISION 常量名引用 (W-9)
- 量化所有验收标准中的模糊项
按方案原文执行 PR 1 (配置验证) 和 PR 2 (proto 对齐)。这两步风险低,可以先行。
在执行 PR 3-5 之前,先补充 P0 测试项。核心变更需要并发测试和失败场景测试保护。
One-instance registration 是最大风险的变更,必须在前面所有 PR 稳定且有回归测试保护后再做。
| 维度 | 评分 | 说明 |
|---|---|---|
| 问题识别 | ★★★★ | 准确识别了 meta-v、revision、cache key 等核心问题 |
| 架构设计 | ★★★☆ | Go-first 方向正确,但并发安全和迁移路径需补强 |
| Java 兼容性 | ★★★☆ | 大部分对齐,key 格式和 hash 算法需明确 |
| 测试计划 | ★★ | 框架存在,但并发/故障/性能测试严重缺失 |
| 实施计划 | ★★★★ | 6 PR 分步合理,风险隔离好 |
| 总体 | ★★★☆ | 方向正确、结构清晰,细节需补强 |
核心建议: 在开始编码前,先修订方案中的 CRITICAL 问题(重点是 revision wire contract、并发安全 API 语义、持久化 cache 迁移),补充 P0 测试基础设施,再按 PR 1→2→3→4→5→6 的顺序执行。PR 6 (one-instance registration) 应作为最后的收敛步骤,不要提前。
永久链接 · 2026-05-24T05:57:56Z
This evaluation is related to the article dubbo-go-metadata-design-spec-zh.version-0
目标仓库:apache/dubbo-go
基准分支:develop
相关讨论:apache/dubbo-go discussions #3300
相关问题示例:
Dubbo 3 正在逐步把服务发现模型从 接口级注册 演进到 应用级注册。
在应用级注册模式下,注册中心应该只保存轻量的应用实例信息。完整的服务元数据,包括导出的服务、协议、端口、方法、参数和服务定义,应通过 metadata 系统获取。
dubbo-go 已经具备 metadata 基础能力:
-
metadata/metadata_service.goMetadataServiceDefaultMetadataService- MetadataService V1 导出
- MetadataService V2 导出
-
metadata/info/metadata_info.goMetadataInfoServiceInfo
-
metadata/client.go- 本地 metadata RPC 获取
- 远程 metadata report 获取
-
registry/servicediscovery/service_discovery_registry.go- provider 侧服务发现注册
-
registry/servicediscovery/service_instances_changed_listener_impl.go- consumer 侧实例变更处理
- 按 revision 获取 metadata
- 生成服务 URL
但是当前实现仍然存在几个问题:
- MetadataService V1 / V2 的导出策略还不够显式。
-
meta-v可能和实际导出的 MetadataService 版本不一致。 -
metadata-service-protocol已经存在于配置中,但不是所有 metadata 初始化路径都会把它传入 metadata options。它的默认行为、测试,以及它和 MetadataService V1/V2 导出策略之间的关系,都需要明确。 -
MetadataInforevision 计算还没有完全对齐 Java Dubbo。 - consumer 侧 metadata cache 只使用
revision,可能导致不同应用之间互相冲突。 -
develop上已经增强了多 provider URL 生成能力,但应用级实例语义、endpoint metadata 和回归覆盖仍需收敛。 - dubbo-go 的 MetadataServiceV2 proto 落后于当前 Java Dubbo 契约。
本设计的目标是让 dubbo-go metadata 机制具备更好的兼容性、确定性和生产可用性。
- 支持 provider 侧 MetadataService V1 和 V2 导出。
- 支持
local和remote两种 metadata 存储模式。 - 支持将应用 metadata 发布到 metadata report。
- 支持 consumer 按应用实例 revision 获取 metadata。
- 支持 Java Dubbo 3.3 consumer -> dubbo-go provider 的 metadata 互操作。
- 支持 dubbo-go consumer -> Java Dubbo 3.3 provider 的 metadata 互操作。
- 支持同一应用下的多个 provider 实例。
- 支持同一应用实例下的多个协议 endpoint。
- 确保
meta-v严格匹配实际导出的 MetadataService 版本。 - 避免 metadata 获取失败导致 consumer panic。
第一阶段不需要实现完整 OpenAPI 生成。
但是 MetadataServiceV2 proto 和服务描述应该对齐 Java Dubbo,并包含 GetOpenAPIInfo。该方法第一阶段可以返回空定义。
当前 dubbo-go 有一个类似如下的 MetadataInfo 模型:
type MetadataInfo struct {
App string
Revision string
Tag string
Services map[string]*ServiceInfo
exportedServiceURLs map[string][]*common.URL
subscribedServiceURLs map[string][]*common.URL
}MetadataInfo 负责记录当前应用导出和订阅的服务。
主要文件:
metadata/info/metadata_info.go
当前 dubbo-go 有一个类似如下的 MetadataService 接口:
type MetadataService interface {
GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]*common.URL, error)
GetExportedServiceURLs() ([]*common.URL, error)
GetSubscribedURLs() ([]*common.URL, error)
Version() (string, error)
GetMetadataInfo(revision string) (*info.MetadataInfo, error)
GetMetadataServiceURL() (*common.URL, error)
}主要文件:
metadata/metadata_service.go
当前 dubbo-go V2 proto 只包含:
service MetadataServiceV2 {
rpc GetMetadataInfo(MetadataRequest) returns (MetadataInfoV2);
}主要文件:
metadata/triple_api/proto/metadata_service_v2.proto
当前 Java Dubbo V2 proto 同时包含:
service MetadataServiceV2 {
rpc GetMetadataInfo(MetadataRequest) returns (MetadataInfoV2);
rpc GetOpenAPIInfo(OpenAPIRequest) returns (OpenAPIInfo);
}因此,dubbo-go 应该对齐 Java Dubbo 的 V2 proto 契约。
dubbo-go 已经定义了重要的 metadata 常量:
ExportedServicesRevisionPropertyName = "dubbo.metadata.revision"
SubscribedServicesRevisionPropertyName = "dubbo.subscribed-services.revision"
MetadataStorageTypePropertyName = "dubbo.metadata.storage-type"
MetadataServiceURLParamsPropertyName = "dubbo.metadata-service.url-params"
MetadataServiceURLsPropertyName = "dubbo.metadata-service.urls"
MetadataVersion = "meta-v"
ServiceInstanceEndpoints = "dubbo.endpoints"主要文件:
common/constant/key.go
这些 key 和 Java Dubbo 的服务实例 metadata 模型整体对齐。
Java Dubbo 应该被视为 wire format、metadata key、MetadataService V2 proto、revision 语义和应用级服务发现行为的互操作契约。不应该把 Java Dubbo 的实现架构直接复制到 dubbo-go。
dubbo-go 实现应保持以下 Go-first 约束:
- metadata 状态继续保留在现有
metadatapackage 中,按registryId组织,并通过同步和快照 helper 增强。不要引入 Java 风格的ApplicationModel或ConfigManager层。 - MetadataService 导出策略和导出能力应实现为当前 metadata/export 路径内的小型 Go struct 或 resolver function。除非现有包结构自然需要,否则不要创建 Java 风格的 exporter framework。
- metadata report 集成继续通过现有
metadata/report.MetadataReport接口完成,并把选中的 report 传递到 registry / service discovery 路径中。consumer 路径不要依赖全局第一个 report。 - one-instance registration 应作为协议、metadata fetch、cache 和 mapping 修复都有测试覆盖之后的最终收敛步骤。第一阶段实现可以在这些前置条件稳定之前保留当前 per-exported-URL registration 作为兼容桥。
- 只有当某个 URL 参数确实是实例级,并且在所有导出服务上只有一个一致值时,才把它提升到 ServiceInstance metadata。如果不同服务值不同,应保留在
ServiceInfo.Params中,并在 consumer 侧从服务 metadata 恢复。 - 除非 Java 兼容性要求修改 wire contract,否则保留当前 dubbo-go 默认行为。例如第一阶段继续让空
metadata-service-protocol解析为dubbo,而不是静默加入 Java 风格的 tri 自动探测。
简而言之:
用 Java Dubbo 对齐协议和 metadata 兼容性。
用 dubbo-go 现有 metadata、registry、report package 边界落地实现。
metadata 流程应为:
Provider Service Export
|
v
MetadataInfo Manager
|
+--------------------+
| |
v v
Local MetadataService Remote MetadataReport
V1 / V2 Nacos / ZK / etcd
| |
+---------+----------+
|
v
ServiceInstance Metadata
revision / storage-type / meta-v / url-params / endpoints
|
v
Consumer ServiceInstancesChangedListener
|
v
Fetch Metadata by revision
|
v
Convert ServiceInfo to provider URLs
|
v
Notify Registry Directory
- 注册中心里的实例 metadata 应保持轻量。
- 完整服务信息应存储在
MetadataInfo中。 - consumer 应使用
revision判断是否需要再次获取 metadata。 -
local模式下,consumer 通过 provider MetadataService RPC 获取 metadata。 -
remote模式下,consumer 从 metadata report 获取 metadata。 -
meta-v必须匹配实际导出的 MetadataService 版本。
配置:
dubbo:
application:
metadata-type: local行为:
- Provider 导出本地 MetadataService。
- Provider 将以下 key 写入服务实例 metadata:
dubbo.metadata.storage-type=localdubbo.metadata.revision={revision}dubbo.endpoints=[...]dubbo.metadata-service.url-params={...}-
meta-v=1.0.0或meta-v=2.0.0 - 可选写入
dubbo.metadata-service.urls=[...],仅用于需要完整 metadata service URL 的兼容场景
- Consumer 收到服务实例事件。
- Consumer 根据实例 metadata 构造 MetadataService URL。
- Consumer 调用 MetadataService RPC 获取完整
MetadataInfo。
配置:
dubbo:
application:
metadata-type: remote
metadata-report:
protocol: nacos
address: 127.0.0.1:8848行为:
- Provider 不需要暴露本地 MetadataService。
- Provider 将
MetadataInfo发布到 metadata report。 - Provider 将以下 key 写入服务实例 metadata:
dubbo.metadata.storage-type=remotedubbo.metadata.revision={revision}dubbo.endpoints=[...]
- Consumer 通过
app + revision从 metadata report 获取MetadataInfo。
主要文件:
metadata/report_instance.go
metadata/client.go
一个已知兼容性问题是:
instance metadata:
meta-v = 2.0.0
provider actually exported:
org.apache.dubbo.metadata.MetadataService, version=1.0.0
这种情况下,Java Dubbo 3.x consumer 可能会尝试调用:
org.apache.dubbo.metadata.MetadataServiceV2:2.0.0
但 dubbo-go provider 实际只导出了 MetadataService V1,可能出现类似错误:
don't have this exporter, key: {app}/org.apache.dubbo.metadata.MetadataServiceV2:2.0.0
因此,meta-v 不能只从业务协议或 endpoint 信息推断,必须来自实际 MetadataService 导出结果。
不要把期望导出的行为和实际导出的能力混在一起。
MetadataServiceExportStrategy 表示 dubbo-go 计划导出什么。它来自:
metadata-typemetadata-service-protocol- 默认协议解析
type MetadataServiceExportStrategy struct {
Protocol string
ExportV1 bool
ExportV2 bool
}建议策略规则,以及请求的导出全部成功时预期的 metadata version:
| metadata-type | metadata-service-protocol | planned Export V1 | planned Export V2 | meta-v from capability |
|---|---|---|---|---|
| local | dubbo | yes | no | 1.0.0 |
| local | tri | yes | yes | 2.0.0 |
| remote | any | no | no | not required |
| local | empty | yes | no | 1.0.0 |
导出前,strategy 必须 normalize 并校验 metadata-service-protocol:
- 空值保持当前 dubbo-go 默认行为,解析为
dubbo。 -
dubbo只导出 MetadataService V1。 -
tri导出 MetadataService V1 + V2。 - 任何其他显式配置的值都必须配置校验失败。不能把它隐式当成 triple,也不应该静默 fallback 到
dubbo,否则会隐藏拼写错误并产生误导性的 metadata capability。
当前 dubbo-go 需要修复的实现细节:metadata.NewOptions() 默认 protocol 是 dubbo,但调用 WithMetadataProtocol("") 会把这个默认值覆盖成空字符串。由于当前 exporter 把非 dubbo 当成 triple,空 protocol 可能意外导出 V1 + V2。option 层或 strategy resolver 必须在导出前 normalize 空值。
V2-only metadata export 不是 dubbo-go 现有配置,第一阶段不应引入。Java Dubbo 有 V2-only metadata export 逻辑,但 dubbo-go phase 1 应使用保守兼容策略:
local + tri 同时导出 V1 和 V2。
只有 V2 实际导出成功时才声明 V2。
第一阶段中,空 metadata-service-protocol 应保持当前 dubbo-go 默认行为,解析为 dubbo / V1。Java Dubbo 可以从已配置的业务协议自动探测 triple,但 dubbo-go 如果要加入同样的自动探测,应作为单独的兼容性变更,并配套显式测试。
如果社区后续需要 V2-only export,应作为单独的兼容性决策提出。
MetadataServiceExportCapability 表示实际导出了什么。只有 exporter 创建成功之后才写入:
type MetadataServiceExportCapability struct {
Protocol string
V1Exported bool
V1URL *common.URL
V2Exported bool
V2URL *common.URL
}这不需要新框架。在 dubbo-go phase 1 中,capability 可以放在现有 metadata package 中,靠近 serviceExporter;由 exportDubbo、exportTripleV1 和 exportV2 设置;再由当前负责写 meta-v 的 ServiceInstance customizer 读取。
硬规则:
meta-v=2.0.0 only if MetadataServiceV2 is actually exported and reachable.
实现不应只从 dubbo.metadata-service.url-params 推断 meta-v。应在导出完成后根据 export capability 解析:
func ResolveMetadataVersion(cap MetadataServiceExportCapability) string {
if cap.V2Exported && cap.V2URL != nil {
return constant.MetadataServiceV2Version
}
if cap.V1Exported && cap.V1URL != nil {
return constant.MetadataServiceV1Version
}
return ""
}如果某个协议导出路径目前无法返回 error,导出代码仍然应该只在 exporter 对象创建成功后记录 capability。V2 导出失败或被跳过时,绝不能发布 meta-v=2.0.0。
当前 ApplicationConfig 已经有类似字段:
MetadataType string
MetadataServicePort string
MetadataServiceProtocol string主要文件:
global/application_config.go
config/application_config.go
当前 develop 有多个 metadata 初始化路径。server.go 在 server startup 期间已经把 Application.MetadataServiceProtocol 传给 metadata options,但 config/metadata_config.go 当前还没有这么做。需要更新它,确保基于 config 的启动路径使用同一个 protocol 值。
当前预期初始化形态:
func initMetadata(rc *RootConfig) error {
opts := metadata.NewOptions(
metadata.WithAppName(rc.Application.Name),
metadata.WithMetadataType(rc.Application.MetadataType),
metadata.WithPort(getMetadataPort(rc)),
metadata.WithMetadataProtocol(rc.Application.MetadataServiceProtocol),
)
return opts.Init()
}第一阶段最小改动是打通并测试所有活跃 metadata 初始化路径,明确默认行为,并在 metadata-service-protocol 为空时保持当前行为。基于 rc.Protocols 的协议自动探测可以后续再加,但必须有显式兼容性测试覆盖。
这必须在依赖后续 server startup 路径之前修复,因为当前 dubbo-go metadata export 被 package-level exportOnce 保护。第一次 Options.Init() 调用会生效;如果第一次调用没有传入 MetadataServiceProtocol,后续即使传入 tri 也不会重新导出 MetadataServiceV2。
MetadataService port 解析也应该感知 protocol。Java Dubbo 的内部 metadata service builder 会先使用显式配置的 metadata service port,然后从选中协议的运行中 server 或 protocol config 获取端口,最后 fallback 到随机端口。Dubbo-go 应保持同样的优先级语义,但不要引入 startup-order coupling:
- 如果配置了
metadata-service-port,使用它。 - 如果
metadata-service-protocol=tri且没有配置 metadata service port,优先使用RootConfig.Protocols中配置的 tri protocol port;只有在不改变启动顺序、不制造 import cycle 的前提下,才使用已经可用的 running tri server port。 - 如果
metadata-service-protocol=dubbo或为空,保留当前 dubbo/default protocol fallback。 - 如果没有匹配的 protocol port,使用随机端口并清晰记录日志。
- 不要把这个 metadata service port 用作应用实例 primary port;primary instance port 仍然从业务 endpoints 中选择。
建议用户配置:
dubbo:
application:
name: demo-provider
metadata-type: local
metadata-service-protocol: tri
metadata-service-port: 20881
protocols:
tri:
name: tri
port: 50051
registries:
nacos:
protocol: nacos
address: 127.0.0.1:8848
registry-type: service主要更新文件:
config/metadata_config.go
metadata/options.go
server/server.go
当前 metadata 使用全局 map,应通过 manager 封装。
建议模型:
type MetadataManager struct {
mu sync.RWMutex
infos map[string]*info.MetadataInfo
}保持现有 package-level API 兼容,并增加 service unexport / unsubscribe 需要的缺失 remove helper:
func GetMetadataInfo(registryId string) *info.MetadataInfo
func AddService(registryId string, url *common.URL)
func RemoveService(registryId string, url *common.URL)
func AddSubscribeURL(registryId string, url *common.URL)
func RemoveSubscribeURL(registryId string, url *common.URL)MetadataInfo 还应暴露不可变发布快照 / deep copy helper:
func (m *MetadataInfo) Clone() *MetadataInfoclone 必须深度复制 App、Revision、Tag、Services、exported service URLs 和 subscribed service URLs,确保发布 remote metadata 或写入 consumer cache 后,不会观察到 provider live metadata object 后续的原地 mutation。
主要文件:
metadata/metadata.go
metadata/info/metadata_info.go
Revision 计算应迁移到 MetadataInfo。
建议 API:
func (m *MetadataInfo) CalAndGetRevision() string {
m.mu.Lock()
defer m.mu.Unlock()
if m.Revision != "" && !m.updated {
return m.Revision
}
if len(m.Services) == 0 {
m.Revision = "0"
m.updated = false
return m.Revision
}
m.Revision = m.calRevisionLocked()
m.updated = false
return m.Revision
}建议确定性计算方式:
func (m *MetadataInfo) calRevisionLocked() string {
keys := make([]string, 0, len(m.Services))
for key := range m.Services {
keys = append(keys, key)
}
sort.Strings(keys)
var builder strings.Builder
builder.WriteString(m.App)
for _, key := range keys {
builder.WriteString(m.Services[key].ToDescString())
}
return revision.Resolve(builder.String())
}ServiceInfo.ToDescString():
func (s *ServiceInfo) ToDescString() string {
return s.GetMatchKey() +
strconv.Itoa(s.Port) +
s.Path +
sortedParamsString(s.Params)
}只有稳定的服务语义应参与 revision。timestamp、进程本地地址等 runtime-only 或 noisy value,以及不会改变可调用服务行为的其他值,必须排除。
不要直接把当前 dubbo-go IncludeKeys 当成最终 revision 参数集合。现有集合包含 timestamp 等值,它们对 URL metadata 有用,但对稳定服务 revision 来说过于 noisy。
对 dubbo-go 来说,第一阶段先在 metadata/info 附近实现一个小型 internal filter function 或静态 include/exclude table,不要新增 Java 风格 SPI:
- 为 revision input 定义显式 service metadata 参数过滤函数。
- 只有当 service-level params 和 method-level params 会影响可调用服务语义时,才把它们纳入 revision。
- 排除 timestamp、pid、bind address、generated local address 等 runtime-only params 和其他进程本地值。
- instance-level params 和 service-level revision params 保持分离。
- 只有当 dubbo-go 有明确用例,并且有第三方定制测试覆盖时,后续再引入扩展 hook。
这更接近 Java Dubbo 当前 MetadataInfo.calRevision() 的设计,即 revision 基于:
app + sorted service desc strings
修改 revision algorithm 会影响 cache。旧 revision cache 和新 revision cache 不能混用。实现上应在 algorithm 变化时清理本地 metadata cache,或给 cache key/storage format 加版本,避免意外复用旧 revision entry。
主要更新文件:
metadata/info/metadata_info.go
registry/servicediscovery/customizer/service_revision_customizer.go
服务名:
org.apache.dubbo.metadata.MetadataService
版本:
1.0.0
用途:
- 保留 MetadataService V1 作为 legacy 和迁移 fallback。
- V2 不可用时,支持 dubbo protocol metadata service。
- 避免破坏仍依赖 V1 metadata 调用的现有 dubbo-go 用户。
Dubbo 2.7 兼容性不是本工作的主要设计目标。主要目标应是 Java Dubbo 3.3 和当前 dubbo-go develop。V1 应作为低成本 fallback 保留,但第一阶段不应增加 2.7 专属行为,也不应把它作为硬性验收要求。
服务名:
org.apache.dubbo.metadata.MetadataServiceV2
版本:
2.0.0
协议:
tri
必需方法:
GetMetadataInfo
GetOpenAPIInfo
建议第一阶段 GetOpenAPIInfo 实现:
func (m *MetadataServiceV2) GetOpenAPIInfo(
ctx context.Context,
req *tripleapi.OpenAPIRequest,
) (*tripleapi.OpenAPIInfo, error) {
return &tripleapi.OpenAPIInfo{Definition: ""}, nil
}主要更新文件:
metadata/triple_api/proto/metadata_service_v2.proto
metadata/metadata_service.go
MetadataInfo conversion 必须在两个方向都保留 ServiceInfo.Port。
Provider 侧 conversion:
func convertV2(serviceInfos map[string]*info.ServiceInfo) map[string]*tripleapi.ServiceInfoV2 {
serviceInfoV2s := make(map[string]*tripleapi.ServiceInfoV2, len(serviceInfos))
for key, serviceInfo := range serviceInfos {
serviceInfoV2s[key] = &tripleapi.ServiceInfoV2{
Name: serviceInfo.Name,
Group: serviceInfo.Group,
Version: serviceInfo.Version,
Protocol: serviceInfo.Protocol,
Port: int32(serviceInfo.Port),
Path: serviceInfo.Path,
Params: serviceInfo.Params,
}
}
return serviceInfoV2s
}Consumer 侧 conversion:
func convertMetadataInfoV2(v2 *tripleapi.MetadataInfoV2) *info.MetadataInfo {
services := make(map[string]*info.ServiceInfo, len(v2.Services))
for key, service := range v2.Services {
services[key] = &info.ServiceInfo{
Name: service.Name,
Group: service.Group,
Version: service.Version,
Protocol: service.Protocol,
Port: int(service.Port),
Path: service.Path,
Params: service.Params,
}
}
return info.NewMetadataInfoWithParams(v2.App, v2.Version, services)
}Provider 应根据 metadata storage mode 写入不同的 service instance metadata。
以下 metadata 应在 local 和 remote 模式下都写入:
dubbo.metadata.storage-type = local | remote
dubbo.metadata.revision = {revision}
dubbo.endpoints = [{"protocol":"tri","port":50051},{"protocol":"dubbo","port":20880}]
dubbo.endpoints 描述同一应用实例暴露的 protocol ports:
[
{"protocol": "tri", "port": 50051},
{"protocol": "dubbo", "port": 20880}
]Consumer URL expansion 应保留 dubbo-go 的 ServiceInstance.ToURLs(serviceInfo) 形态,但调整选择规则以匹配 Dubbo 3 metadata contract:
- 如果
ServiceInfo.Port > 0,使用ServiceInfo.Protocol和ServiceInfo.Port构造 URL。 - 如果
ServiceInfo.Port缺失或为 0,根据ServiceInfo.Protocol从dubbo.endpoints中选择 endpoint。 - 如果同一个 protocol 存在多个 endpoint,
ServiceInfo.Port是唯一可靠的消歧方式;测试必须覆盖该情况,或者显式拒绝 same-protocol multi-port export。
Dubbo-go customizer 必须从当前 instance 的 ServiceMetadata / 当前 registry MetadataInfo 计算 dubbo.endpoints,不能从全局 metadata service 的聚合 exported URL list 计算。当前 DefaultMetadataService.GetExportedServiceURLs() 会跨 registry id 聚合 metadata,因此在 instance customizer 中使用它可能把一个 registry context 的 endpoint 或 revision 泄漏到另一个 context。
dubbo.subscribed-services.revision 和 dubbo.metadata.revision 不是同一个值,不应该从 exported-services revision 复制。如果 dubbo-go 保留该 key 用于 subscribed URL metadata,它必须从 subscribed URLs 独立计算,不应被当成通用 provider instance metadata。
主要文件:
registry/service_instance.go
registry/servicediscovery/customizer/
当 metadata-type=local 且本地 MetadataService 已导出时,provider 还应额外写入:
dubbo.metadata-service.url-params = {...}
meta-v = 1.0.0 | 2.0.0
这些 key 用于 consumer 构造 MetadataService URL,并从 provider 获取完整 MetadataInfo。
dubbo.metadata-service.urls 是可选兼容 metadata,不是每个 dubbo-go local provider 都应该默认写出的标准字段。
示例:
{
"protocol": "tri",
"port": "20881",
"version": "2.0.0",
"release": "dubbo-golang-3.x.x"
}主要文件:
registry/servicediscovery/customizer/metadata_service_url_params_customizer.go
该 JSON 必须描述 metadata service endpoint,而不是 business service endpoint。如果 V1 和 V2 都通过 triple 导出,该字段可以指向优先使用的 V2-capable endpoint,但 export capability 仍然是 meta-v 的事实来源。
dubbo.metadata-service.urls 是 Java Dubbo 为 Dubbo Spring Cloud 和 metadata discovery 兼容使用的兼容字段。标准 dubbo-go 应用级注册不应默认写出它。
重要 Java 兼容性说明:
Java Dubbo 不会把 dubbo.metadata-service.urls 当成 url-params 后面的 fallback。
如果 dubbo.metadata-service.urls 存在,Java Dubbo 可能选择 SpringCloudMetadataServiceURLBuilder 路径。
因此,dubbo-go 只有在该值确实是可完整使用的 metadata service URL list,并且目标就是该兼容路径时,才应写入该 key。
推荐格式:
[
"tri://127.0.0.1:20881/org.apache.dubbo.metadata.MetadataServiceV2?group=demo-provider&interface=org.apache.dubbo.metadata.MetadataServiceV2&version=2.0.0",
"tri://127.0.0.1:20881/org.apache.dubbo.metadata.MetadataService?group=demo-provider&interface=org.apache.dubbo.metadata.MetadataService&version=1.0.0&serialization=hessian2"
]规则:
- 该值是 metadata service URL string 的 JSON array。
- URL 描述 MetadataService endpoint,而不是 business service endpoint。
- 如果 V2 和 V1 都已导出,优先列出 V2 URL,再列出 V1。
- 当该 key 存在时,Java Dubbo consumer 必须能够直接使用这个 URL list。
- dubbo-go consumer 可以在
url-params缺失、非法或无法构造可用 MetadataService URL 时,把这个 key 作为 local fallback。 - 解析出的 URL capability 仍必须和
meta-v校验;urls不能导致在 V2 实际没有导出时推断出meta-v=2.0.0。
meta-v 应来自实际 export capability:
func ResolveMetadataVersion(cap MetadataServiceExportCapability) string {
if cap.V2Exported && cap.V2URL != nil {
return constant.MetadataServiceV2Version
}
if cap.V1Exported && cap.V1URL != nil {
return constant.MetadataServiceV1Version
}
return ""
}主要文件:
registry/servicediscovery/customizer/metadata_service_version_customizer.go
当前 customizer 从 dubbo.metadata-service.url-params 推导 meta-v。这不充分,因为 URL params 可以写 tri,但 V2 export 未必真的成功。customizer 应读取已记录的 metadata service export capability。
当 metadata-type=remote 时,provider 不需要 MetadataService RPC metadata key:
dubbo.metadata-service.url-params
dubbo.metadata-service.urls
meta-v
完整 MetadataInfo 会发布到 metadata report,consumer 使用 app + revision 获取。
预期流程:
ServiceOptions.Export()
-> build provider URL
-> registry protocol Register(url)
-> metadata.AddService(registryId, url)
-> serviceNameMapping.Map(url)
主要文件:
server/action.go
registry/servicediscovery/service_discovery_registry.go
metadata/metadata.go
应用级服务发现仍然需要 interface-to-application mapping。
这里有两条独立的发现链路:
interface -> provider applications mapping
provider application instance -> MetadataInfo
Provider 侧:
- 每个 exported provider URL 仍必须写入 service name mapping。
- 注册一个 ServiceInstance 并不意味着可以移除
serviceNameMapping.Map(url)。 -
MetadataInfo描述一个应用导出了什么,但 mapping 告诉 consumer 应该订阅哪些应用。
Consumer 侧:
- 如果配置了
provided-by,consumer 可以直接订阅这些应用。 - 如果没有配置
provided-by,consumer 必须通过 service name mapping 解析 provider applications。 - 解析出 applications 后,consumer 订阅 application instances,并按
{providerApp}:{revision}获取MetadataInfo。
Remote metadata report 和 service name mapping 是两类职责:
- Metadata report 存储和获取完整
MetadataInfo。 - Service name mapping 解析 interface -> provider applications。
- 它们可以共享同一个后端,但不能被当成同一份数据。
当前 dubbo-go 默认的 metadata-based service name mapping 依赖 metadata report instances。没有 metadata report backend 时,consumer 应使用 provided-by 或其他显式 mapping source。因此,mapping 的验收测试应包含一个配置好的、支持 service app mapping 的 metadata report backend。
Mapping listener 能力和 backend 有关。当前 dubbo-go 中 Nacos 和 Zookeeper 有 listener-oriented 实现,而 Etcd 没有提供同等动态 mapping listener 行为。因此,验证动态 interface -> application mapping 的测试必须指定支持 listener 的 backend,而不是假设所有 metadata report backend 行为相同。
多 registry 场景下,mapping lookup 和 mapping listener 也应该使用当前 registry/service discovery context 关联的 metadata report,而不是全局第一个 metadata report。当前 provider mapping 会写入所有 metadata reports,因为 exported provider URLs 不携带 registry id;但 consumer 侧 Get / listener registration 仍需要使用正确 backend,避免监听错误的 mapping source。
建议 provider 侧注册流程:
func (s *serviceDiscoveryRegistry) RegisterService() error {
registryId := s.url.GetParam(constant.RegistryIdKey, constant.DefaultKey)
metaInfo := metadata.GetMetadataInfo(registryId)
if metaInfo == nil {
return fmt.Errorf("metadata info not found, registry id = %s", registryId)
}
revision := metaInfo.CalAndGetRevision()
if metadata.GetMetadataType() == constant.RemoteMetadataStorageType {
report := metadata.GetMetadataReportByRegistry(registryId)
if report == nil {
return errors.New("metadata report not found")
}
if err := report.PublishAppMetadata(metaInfo.App, revision, metaInfo.Clone()); err != nil {
return err
}
}
instance := createInstance(metaInfo)
instance.GetMetadata()[constant.ExportedServicesRevisionPropertyName] = revision
return s.serviceDiscovery.Register(instance)
}Application instance registration 必须做到每个进程、每个 registry 幂等。
Java Dubbo 把生命周期拆成:
registerMetadataAndInstance -> serviceDiscovery.register()
refreshMetadataAndInstance -> serviceDiscovery.update()
unregisterMetadataAndInstance -> serviceDiscovery.unregister()
Dubbo-go 应遵循同样的语义模型:
- 初始 provider service export 将 exported URLs 收集到
MetadataInfo。 - 第一次 application registration 为进程注册一个 ServiceInstance。
- 后续 service export/unexport 或 metadata 语义变化会重新计算 revision。
- 如果 revision 或 instance metadata 变化,remote 模式下先发布更新后的 metadata。
- 然后更新或重新注册已有 ServiceInstance,而不是为同一个进程新增另一个 instance。
- shutdown 时注销这个单一 application instance。
如果某个 registry implementation 没有 native Update 操作,dubbo-go 可以用相同 stable instance ID 执行 unregister + register 来实现 refresh。
重要语义变更:
一个应用进程应只注册一个应用实例。
这是目标状态,不是第一步必须完成的实现。不要在 metadata fetch、cache key、endpoint expansion 和 service name mapping 都有回归覆盖之前切换到该模型。早期 PR 中,dubbo-go 可以保留当前 per-exported-URL registration 作为兼容桥。
多协议和多端口应通过:
dubbo.endpoints
表示。
这是 dubbo-go 在 Dubbo 3 应用级服务发现上长期应收敛的模型。实现仍需要尊重 dubbo-go 当前 registry abstraction 和 rollout 风险:短期实现可以继续把每个 exported service URL 注册为一个 instance 作为 bug-fix bridge,但这不是目标设计,因为注册中心应存应用实例,而 Dubbo metadata 应描述服务和 endpoints。
当每个进程只注册一个 application instance 时,provider 必须选择稳定的 primary instance address。
这和 Java Dubbo ServiceInstanceHostPortCustomizer 的方向一致:如果 instance 还没有 port,就从 exported business service URLs 里选一个,优先选择配置的 application protocol,找不到则 fallback 到第一个 exported URL。
建议规则:
-
Host:使用进程注册 IP / advertised host。 -
Port:使用 primary business endpoint。优先使用配置的 preferred/default business protocol endpoint port;如果不可用,使用第一个 exported service URL port。metadata-service port 不应作为 primary instance port,除非不存在 business endpoint。 -
ID:使用稳定 instance ID,优先为{host}:{primaryPort},除非 registry implementation 需要其他格式。 - 所有实际 business protocol ports 必须写入
dubbo.endpoints。 - MetadataService address 在 local 模式下通过
dubbo.metadata-service.url-params写入,dubbo.metadata-service.urls仅用于可选兼容场景。它不能作为主要 instance identity。 - Instance-level metadata 只能包含整个 application instance 一致的值。service-specific 或冲突值必须保留在
ServiceInfo.Params。
原因:
ServiceInstance 主地址代表应用实例身份。
MetadataService 只是用于获取 metadata 的内部地址。
如果把 metadata-service port 作为 primary port,instance identity 可能变得不稳定,尤其是 metadata-service port 是随机端口时。
当前 consumer 流程应保留并增强:
Interface subscribe
-> if provided-by exists, use configured applications
-> otherwise resolve applications through service name mapping
-> subscribe provider application instances
ServiceInstancesChangedEvent
-> group instances by app + revision
-> get MetadataInfo from cache
-> if cache miss:
local: fetch from MetadataService RPC
remote: fetch from MetadataReport
-> MetadataInfo.Services
-> instance.ToURLs(serviceInfo), preferring ServiceInfo.Port when present
-> notify registry directory
主要文件:
registry/servicediscovery/service_instances_changed_listener_impl.go
Metadata fetch 应保留当前基于 storage-type 的判断:
if storageType == constant.RemoteMetadataStorageType {
metadataInfo, err = metadata.GetMetadataFromMetadataReport(revision, instance)
} else {
metadataInfo, err = metadata.GetMetadataFromRpc(revision, instance)
}主要文件:
metadata/client.go
registry/servicediscovery/service_discovery_registry.go
registry/servicediscovery/service_instances_changed_listener_impl.go
Remote metadata fetch 必须使用当前 registry / service discovery context 关联的 metadata report,而不是任意全局第一个 report。Java Dubbo 在获取 remote metadata 时会把 metadata report 通过当前 service discovery flow 传递下去。Dubbo-go 应避免把 GetMetadataReport() 作为唯一 lookup path;需要把 registry id、registry cluster 或已选中的 metadata report 传入 listener/client 路径,确保 provider publish 和 consumer fetch 使用同一个 backend。
建议 fallback 顺序:
1. 如果 meta-v=2.0.0,优先调用 MetadataServiceV2.GetMetadataInfo。
2. 如果 V2 失败且存在 V1 参数,fallback 到 MetadataService V1。
3. 如果响应是 string,为迁移兼容按 JSON 解析。
4. 如果某个 instance 失败,尝试同一 app + revision 下的其他 instance。
5. 如果所有尝试都失败,跳过该 revision 并记录 warning,不要 panic。
必需实现细节:
1. 如果 dubbo.metadata-service.url-params 缺失或非法,不要解引用 nil URL。
2. 标准路径优先使用 dubbo.metadata-service.url-params。
3. 对 dubbo-go consumer,如果 url-params 不能构造可用 URL,且 optional key 存在,则尝试 dubbo.metadata-service.urls。
4. V2 fallback 到 V1 时,用以下信息重建 URL:
- interface/path = org.apache.dubbo.metadata.MetadataService
- version = 1.0.0
- method = getMetadataInfo
5. 只有返回非 nil 且包含 services 的 MetadataInfo 后,才写入 cache。
当前 cache 不应只使用 revision。
推荐 cache key:
{app}:{revision}
app 部分必须是 provider application,通常是 instance.GetServiceName(),而不是 listener 中保存的 consumer application。
建议 helper:
func buildMetadataCacheKey(instance registry.ServiceInstance, revision string) string {
return instance.GetServiceName() + ":" + revision
}两层 cache 都应使用这个 key:
cacheKey := buildMetadataCacheKey(instance, revision)
if metadataInfo, ok := metaCache.Get(cacheKey); ok {
return metadataInfo.(*info.MetadataInfo), nil
}
metaCache.Set(cacheKey, metadataInfo)ServiceInstancesChangedListenerImpl.revisionToMetadata 也应使用 {app}:{revision} 作为 key。否则内存 cache 仍然可能把不同 provider application 中碰巧 revision 相同的 metadata 混在一起。
cache manager 作用域也需要检查。当前 dubbo-go 使用 package-level metaCache 和 cacheOnce,并用第一个 app name 初始化。改成 {app}:{revision} key 后,要么使用一个明确 app-neutral 的共享 metadata cache 文件,要么按 provider app 维护 cache manager。不能让第一个订阅到的 app 决定后续所有 provider app 的持久化 cache storage。
原因:
不同应用可能生成相同 revision。
只使用 revision 会导致跨应用 metadata cache 冲突。
| Provider | Consumer | metadata-type | MetadataService | 预期 |
|---|---|---|---|---|
| dubbo-go | dubbo-go | local | V1/V2 | OK |
| dubbo-go | dubbo-go | remote | metadata report | OK |
| dubbo-go | Java Dubbo 3.3 | local + tri | V2 preferred, V1 fallback | OK |
| dubbo-go | Java Dubbo 3.3 | remote | metadata report | OK |
| Java Dubbo 3.3 | dubbo-go | local | V2 preferred, V1 fallback | OK |
| Java Dubbo 3.3 | dubbo-go | remote | metadata report | OK |
| dubbo-go | Java Dubbo 2.7 | local | V1 | Best effort / legacy fallback |
| Java Dubbo 2.7 | dubbo-go | local | V1 | Best effort / legacy fallback |
关键兼容性规则:
meta-v 必须匹配实际导出的 MetadataService version。
Java Dubbo 2.7 兼容性应视为 legacy migration support。本设计不应围绕 Dubbo 2.7 应用级服务发现优化,因为目标基准是 Dubbo 3.x metadata 和 service-discovery 行为。
Metadata 是服务发现关键路径的一部分。单个异常 provider instance 不应导致 consumer 崩溃。
必需行为:
- Metadata RPC 失败返回 error,不 panic。
- Metadata 反序列化失败时跳过当前 instance。
- 如果一个 instance 失败,尝试同一
app + revision下的其他 instance。 - 空 metadata 不应写入 cache。
- 缺失 revision 时记录 warning 并跳过。
-
meta-v=2.0.0且 V2 call 失败时,应在可能的情况下尝试 V1 fallback。 - 当 optional key 存在时,dubbo-go consumer 可以从非法
url-paramsfallback 到dubbo.metadata-service.urls。
建议指标:
metadata.push.rt
metadata.subscribe.rt
metadata.rpc.fetch.rt
metadata.report.fetch.rt
metadata.cache.hit
metadata.cache.miss
metadata.fetch.error
建议日志:
[METADATA_REGISTER] metadata revision changed: old -> new, app: demo, services: 3
[METADATA_REGISTER] publish remote metadata app=demo revision=xxx
[METADATA_SERVICE] export V1 url=...
[METADATA_SERVICE] export V2 url=...
[METADATA_SUBSCRIBE] fetch metadata app=demo revision=xxx storage=local
[METADATA_SUBSCRIBE] cache hit app=demo revision=xxx
[METADATA_SUBSCRIBE] fallback V2 -> V1 app=demo revision=xxx
-
AddService正确更新 services。 -
RemoveService正确更新 services。 - 相同 services 以不同插入顺序加入时生成相同 revision。
- 参数变化会导致 revision 变化。
- 方法级参数变化会导致 revision 变化。
- runtime-only 参数变化,例如 timestamp 或 bind address,不会导致 revision 变化。
- 空 services 返回 revision
"0"。 -
MetadataInfo.Clone()返回 publish/cache snapshot,后续AddService/RemoveService调用不会改变该 snapshot。
-
metadata-service-protocol=dubbo只导出 V1。 -
metadata-service-protocol=tri导出 V1 + V2。 -
metadata-service-protocol=tri且没有metadata-service-port时,如果配置了 tri protocol port,则使用它。 - 不支持的
metadata-service-protocol值不会静默导出 V2。 -
metadata-type=remote不导出本地 MetadataService。 - Export capability 只在 exporter 创建成功后记录。
-
meta-v匹配已记录的 export capability。 - V2 export failure 绝不会产生
meta-v=2.0.0。 - 标准 dubbo-go registration 不默认写出
dubbo.metadata-service.urls。 - 当写出
dubbo.metadata-service.urls时,它是 metadata service URL string 的 JSON array,并且 Java Dubbo Spring Cloud compatibility builder 可以直接使用。 - Dubbo-go consumer 优先使用
url-params,只有当url-params不可用或无法使用时才 fallback 到urls。
-
MetadataInfo -> MetadataInfoV2不丢字段。 -
MetadataInfoV2 -> MetadataInfo不丢字段。 -
ServiceInfo.Port、Path和Params正确转换。 - V2 conversion 保留
ServiceInfo.Port。
- 相同 app 和相同 revision 复用 metadata。
- 不同 app 即使 revision 相同,也不共享 metadata cache entries。
- nil 或空 metadata 不写入 cache。
- revision algorithm 改变后,不复用旧 algorithm 生成的本地 metadata cache entries。
- 一致的 instance-level metadata,例如所有 exported services 上只有一个
environment值时,会写入 application instance,并复制到生成的 consumer URLs。 - 冲突的 per-service values 不会提升到 ServiceInstance metadata;它们保留在各自服务的
ServiceInfo.Params中。 - one-instance registration 不会把第一个 exported URL 的任意参数复制到整个 application instance。
- ServiceInstance customizers 从 instance 自己的
ServiceMetadata/ 当前 registry metadata 计算 revision 和 endpoints,而不是从全局聚合 exported URLs 计算。
- Provider export 仍会为每个 exported provider URL 调用 service name mapping。
- 当没有配置
provided-by且 metadata report backend 可用时,consumer 通过 service name mapping 解析 provider applications。 - Metadata report data 和 service name mapping data 通过独立的逻辑 API 存取。
- Dynamic mapping listener 测试使用支持 mapping listener 的 backend,例如 Nacos 或 Zookeeper;没有等价 listener 能力的 backend 应由 fallback behavior tests 覆盖。
- Multi-registry mapping lookup/listener 使用当前 registry/service discovery context 关联的 metadata report。
Go provider:
metadata-type=local
metadata-service-protocol=tri
Go consumer:
service discovery subscribe
fetch MetadataServiceV2
generate provider URL
invoke successfully
Go provider:
metadata-type=remote
metadata-report=nacos
Go consumer:
service discovery subscribe
fetch metadata from metadata report
invoke successfully
还应覆盖 multi-registry remote metadata:
registry A 将 app metadata 发布到 metadata report A
registry B 将 app metadata 发布到 metadata report B
通过 registry B 订阅的 consumer 必须从 metadata report B 获取,
而不是从全局第一个 metadata report 获取。
Java Dubbo 3.3 consumer -> Go provider V2
Java Dubbo 3.3 consumer -> Go provider V1 fallback
Go consumer -> Java Dubbo 3.3 provider V2
Go consumer -> Java Dubbo 3.3 provider V1 fallback
Dubbo 2.7 应继续作为 best-effort legacy 场景,在可行时由 V1 fallback 覆盖,但不应阻塞第一阶段验收标准。
same application, 3 provider instances:
port=20000
port=20001
port=20002
consumer 应看到 3 个 provider URLs。
同一个 provider 暴露:
tri:50051
dubbo:20880
instance metadata 应包含:
dubbo.endpoints=[tri, dubbo]
consumer 应根据 ServiceInfo.Protocol 生成 URLs。
同一个 provider 在不同端口暴露同一个 protocol:
tri:50051 for GreetService
tri:50052 for UserService
consumer 应根据 ServiceInfo.Protocol + ServiceInfo.Port 生成 URLs。
consumer reference without provided-by:
interface=org.apache.dubbo.samples.GreetService
metadata report backend:
service app mapping is enabled
mapping:
GreetService -> greet-provider
consumer 应订阅 greet-provider instances,
按 greet-provider:{revision} 获取 MetadataInfo,
并成功调用。
范围:
config/metadata_config.go
metadata/options.go
server/server.go
任务:
- 保留并测试当前把
Application.MetadataServiceProtocol传给 metadata options 的路径。 - 同样通过
config/metadata_config.go传递Application.MetadataServiceProtocol。 - 增加回归测试,证明第一个 metadata
Options.Init()调用在exportOnce阻止重新导出前已经收到配置的 protocol。 - 确保
metadata-type=remote不导出本地 metadata service。 - 确保
WithMetadataProtocol("")不会覆盖默认 dubbo protocol,也不会意外导出 triple MetadataService。 - 当
metadata-service-protocol为空时,保留现有默认行为:phase 1 解析为 dubbo / V1,而不是 Java 风格 protocol auto-detection。 - 校验 metadata service protocol 值,任意非 dubbo/non-tri 值都应 fail fast,而不是静默导出 triple 或 fallback 到 dubbo。
- 让 metadata service port fallback 感知 protocol:显式 metadata service port 优先,然后是选中 metadata service protocol port,最后随机端口并记录清晰 warning。
- 增加单元测试。
范围:
metadata/triple_api/proto/metadata_service_v2.proto
metadata/metadata_service.go
任务:
- 增加
GetOpenAPIInfo。 - 增加
OpenAPIRequest。 - 增加
OpenAPIInfo。 - 增加
OpenAPIFormat。 - 实现空
GetOpenAPIInfo。 - 在
MetadataInfo -> MetadataInfoV2conversion 中保留ServiceInfo.Port。 - 在
MetadataInfoV2 -> MetadataInfoconversion 中保留ServiceInfo.Port。 - 重新生成 generated code。
- 更新 service descriptor。
范围:
registry/servicediscovery/customizer/metadata_service_version_customizer.go
metadata/metadata_service.go
metadata/options.go
任务:
- 增加
MetadataServiceExportStrategy表示计划导出行为。 - 增加
MetadataServiceExportCapability存储实际导出结果。 - 只在 exporter 创建成功后记录 capability。
- 根据实际 export capability 生成
meta-v。 - 确保只有 V2 已导出时才写出
meta-v=2.0.0。 - 停止只从
dubbo.metadata-service.url-params推导meta-v。 - 将
dubbo.metadata-service.urls定义为可选兼容 JSON array,内容为 metadata service URL strings。 - 标准 dubbo-go registration 不默认写出
dubbo.metadata-service.urls。 - 如果写出
dubbo.metadata-service.urls,确保 Java Dubbo 可以直接使用 URL list。 - 保持 dubbo-go phase 1 策略:local + tri 导出 V1 + V2,不引入 V2-only export。
- 增加 Java Dubbo 3.3 consumer 兼容测试。
范围:
metadata/client.go
registry/servicediscovery/service_discovery_registry.go
registry/servicediscovery/service_instances_changed_listener_impl.go
任务:
- 将持久化 cache key 改为 provider
{app}:{revision}。 - 将
revisionToMetadata内存 map key 改为 provider{app}:{revision}。 - 修复 cache manager 作用域,避免 package-level
cacheOnce让第一个订阅到的 app 拥有所有 provider app 的持久化 cache storage。 - 增加 V2 -> V1 fallback。
- 避免 nil metadata panic。
- 对 dubbo-go consumer,当
url-params非法时,如果 optional key 存在,则 fallback 到dubbo.metadata-service.urls。 - 将
dubbo.metadata-service.urls解析为 metadata service URL string 的 JSON array,并按meta-v和 fallback 顺序选择 V2/V1。 - 不缓存 nil 或空 metadata。
- 尝试同 revision 下的其他 instance。
- 使用当前 registry/service discovery context 关联的 metadata report,而不是全局第一个 metadata report。
- 增加集成测试。
范围:
metadata/metadata.go
metadata/info/metadata_info.go
registry/servicediscovery/customizer/service_revision_customizer.go
任务:
- 用同步机制封装全局 metadata maps,并增加 package-level
RemoveService/RemoveSubscribeURLhelper。 - 增加
CalAndGetRevision()。 - 增加
MetadataInfo.Clone(),或等价的 publish snapshot helper,用于 remote metadata publication 和 consumer cache write。 - 增加
ServiceInfo.ToDescString()。 - 替换 customizer revision 逻辑,让 exported-services revision 从当前 instance
ServiceMetadata/ registryMetadataInfo计算,而不是从全局聚合 exported URLs 计算。 - 增加轻量 internal service metadata parameter filter,排除 timestamp、pid、bind address、generated local address 等 runtime-only params。phase 1 不增加新的 extension SPI。
- 清理或 version local metadata cache,避免旧 revision entries 和新 revision entries 混用。
- 增加确定性 revision 测试。
范围:
registry/servicediscovery/service_discovery_registry.go
registry/service_instance.go
registry/servicediscovery/customizer/
metadata/mapping/metadata/service_name_mapping.go
任务:
- 用回归测试保护
develop的 multi-provider URL generation 行为。 - 将 provider registration 逐步迁移到每个进程一个 application instance。
- 将所有 exported services 存储到
MetadataInfo。 - 将 protocol ports 存储到
dubbo.endpoints,并让 consumer URL expansion 在ServiceInfo.Port存在时优先使用它。 - 从当前 instance
ServiceMetadata/ registry metadata 计算dubbo.endpoints,不要从metadata.GetMetadataService().GetExportedServiceURLs()计算。 - 选择稳定的 ServiceInstance primary address:host 来自进程注册 IP / advertised host;port 来自 preferred/default business protocol endpoint,否则使用第一个 exported service URL;只有没有 business endpoint 时才使用 metadata-service port;ID 优先使用
{host}:{primaryPort},除非 registry 要求其他格式。 - 每个进程、每个 registry 保持一个已注册 application instance。
- metadata 变化通过 service discovery update 刷新;如果没有 native update,则使用相同 stable instance ID 执行 unregister + register。
- revision 变化或导出额外服务时,不追加重复 ServiceInstances。
- instance-level metadata,例如
environment,只有在所有 exported services 上只有一个一致值时才写到 registered application instance。如果不同 exported service 的值不同,保留在ServiceInfo.Params并在 consumer 侧从 service metadata 恢复。 - 为每个 exported provider URL 保留
serviceNameMapping.Map(url)。 - 验证当没有
provided-by,且配置了支持 service app mapping 的 metadata report backend 时,consumer 可以通过 service name mapping 发现。 - 在支持 listener 的 backend 上验证 dynamic mapping listener 行为,例如 Nacos 或 Zookeeper;对没有 listener 支持的 backend 验证 fallback behavior。
- 验证 multi-registry service name mapping lookup/listener 不使用无关的全局第一个 metadata report。
- 在改变 registration semantics 之前,增加 Nacos multi-provider 和 multi-protocol 集成测试。
- 不要把 registration semantics 改动和 metadata protocol 或
meta-v修复放在同一个 PR。
- 当配置
metadata-service-protocol: tri时,provider 实际导出MetadataServiceV2。 - 当
meta-v=2.0.0时,Java Dubbo 3.3 consumer 可以调用 dubbo-go provider 的MetadataServiceV2.GetMetadataInfo。 - 当配置
metadata-service-protocol: dubbo时,不得写出meta-v=2.0.0。 - 当配置
metadata-service-protocol: tri且省略metadata-service-port时,metadata service port fallback 使用可用的 tri protocol port,而不是 dubbo protocol port。 - 当
metadata-type=remote时,provider 不依赖本地 MetadataService,consumer 可以从 metadata report 获取 metadata。 - 在 multi-provider 场景下,consumer directory 能看到所有 provider URLs,并且有回归测试保护。
- 在 multi-protocol 场景下,consumer 根据
ServiceInfo.Protocol选择正确 endpoint,并使用ServiceInfo.Port消歧 same-protocol multi-port services。 - Metadata fetch failures 不会 panic。
- 相同
{app}:{revision}不会触发重复 metadata fetch。 - Revision 变化会触发 metadata refresh。
- Go / Java Dubbo 3.3 双向 metadata 兼容测试通过。
- Remote metadata fetch 使用当前 registry/service discovery context 关联的 metadata report。
- 不同 applications 即使 revision 相同,也不会共享 cached metadata。
- Metadata cache manager scope 是 app-neutral 或 per-provider-app;不会意外绑定到第一个 subscribed app。
- 长期应用级注册使用每个 application process 一个 ServiceInstance,services 存储在
MetadataInfo,ports 存储在dubbo.endpoints。 - One-instance registration 选择稳定 primary business endpoint 作为 ServiceInstance address,不优先使用 metadata-service port 作为 primary port。
- 当没有配置
provided-by,并且配置了支持 mapping 的 metadata report backend 时,consumer 可以通过 service name mapping 发现 provider applications。 - Multi-registry service name mapping lookup/listener 使用当前 registry/service discovery context 关联的 metadata report,并且 dynamic listener 断言仅限支持 mapping listener 的 backend。
- Revision algorithm 变化不会复用旧 algorithm 的 stale local metadata cache entries。
- Service export/unexport 刷新现有 application instance metadata 和 revision,而不是注册 duplicate instances。
- Remote metadata publication 和 consumer metadata cache 存储稳定 metadata snapshots,而不是共享的 mutable provider metadata objects。
- One-instance registration 只提升 application instance 级一致的 metadata values;冲突的 service-level values 保留在
ServiceInfo.Params。 - Revision 和 endpoint customizers 使用当前 instance / registry metadata,不会跨 registry contexts 泄漏全局聚合 exported URLs。
-
dubbo.metadata-service.urls有明确 JSON URL-list 格式,不默认写出;一旦写出,Java Dubbo 的 Spring Cloud compatibility path 可以直接使用。
推荐顺序:
- 验证
metadata-service-protocolwiring 和默认行为,并补测试。 - 对齐 MetadataServiceV2 proto 与 Java Dubbo,包括
GetOpenAPIInfo和ServiceInfo.Portconversion。 - 增加 export strategy / capability recording,并根据实际 export capability 修复
meta-v生成。 - 改进 consumer metadata fetch、fallback 和
{providerApp}:{revision}cache keys。 - 将 revision calculation 移入
MetadataInfo,并清理或 version 旧 metadata cache。 - 最后收敛 provider registration 到每个进程一个 application instance,并由 multi-provider、multi-protocol 和 service name mapping 回归测试保护。
这些改动足够小,社区更容易接受;同时也足够重要,可以修复真实的 Java / Go 互操作问题。
dubbo-go 不需要从零重建 metadata。
正确方向是增强现有架构:
MetadataInfo
+ MetadataService V1/V2
+ ServiceInstance metadata
+ MetadataReport
+ Consumer metadata fetch by revision
最重要的设计规则是:
Service instance metadata 必须描述实际 provider capability。
具体来说:
meta-v=2.0.0 表示 MetadataServiceV2 已实际导出且可访问。
只要保证这一点,Java Dubbo 和 dubbo-go 的 metadata 互操作就会更可预测。
永久链接 · 2026-05-31T09:35:29Z
复盘下来,删减的直接原因很明确:#2534 是按 #2432 的方案做的一次主动瘦身,不是误删。
时间线大概是这样:
- 2023-09 的 #2432 先把当时 metadata 模块梳理了一遍,指出 metadata 逻辑散在
common/extension、config/instance、metadata/definition、metadata/identifier、metadata/service、registry/event等很多地方。 - #2432 的评论里给了“优化 draft”:目标是简化 metadata center 处理逻辑,把 metadata 逻辑集中到
metadata模块。 - 2024-03 的 #2534 落地了这个方案,commit 是
d6464e40 Refactor metadata,一次性1703 insertions / 5227 deletions。 - 后面 #2760 合
dev-3.3时,又把这套删减后的形态带进开发线,所以现在看起来就是 metadata 被“大幅删了”。
它当时的设计假设是:dubbo-go 3.x 主要服务发现路径已经转向应用级服务发现,所以 metadata center 不再重点存 provider/consumer/service-definition 级元数据,只保留应用级 MetadataInfo、service-app mapping、MetadataService。
所以被删的核心东西是:
-
metadata/definition:provider service definition / method definition 这套被判断为“不再需要” -
metadata/identifier:老的 service/provider/consumer identifier 被判断为“不再需要” -
metadata/service/local|remote|exporter:拆开的 MetadataService 本地/远程/exporter 抽象被压扁到 metadata_service.go(原文作者本地路径:/dubbo-go/metadata/metadata_service.go:51) -
metadata/report/delegate:带失败重试、周期上报、provider/consumer metadata 上报的 delegate 层被删 -
common/extension/metadata_*、config/instance/metadata_report*:扩展点和全局实例缓存被认为太分散,迁到/收缩到 metadata 包内部
保留下来的新模型就是现在这套:MetadataInfo 作为应用级服务发现元数据容器,见 metadata_info.go(原文作者本地路径:/dubbo-go/metadata/info/metadata_info.go:58);MetadataReport 只负责 app metadata 和 mapping,见 report.go(原文作者本地路径:/dubbo-go/metadata/report/report.go:29);服务发现过程通过 revision 拿远端 app metadata。
我觉得当时这个删减有它的工程动机:老 metadata 模块确实重、散、和 config/registry/server lifecycle 纠缠很深,而且很多能力对 dubbo-go 当时的应用级服务发现主路径不是必需的。
永久链接 · 2026-06-25T09:08:40Z