Lazy loaded image
梳理我对Dubbo的理解
字数 4094阅读时长 11 分钟
2026-3-17
type
Post
status
Published
date
Mar 17, 2026
slug
IdeaAboutDubbo
summary
tags
技术探索
推荐
category
icon
password

从调用链路、metadata 到 Triple 路由,梳理我当前的服务治理理解

这篇文章主要基于 dubbo-go 当前实现来梳理我的理解,因此结论首先成立于我接触比较多的 dubbo-go 调用链路,而不是对整个 Dubbo 生态所有实现的完整概括。我的目标是解释:我为什么开始把 Dubbo 从“RPC 框架”理解为“围绕调用构建的服务治理运行时”。
我以前把 Dubbo 理解成一个 RPC 框架:定义接口,启动 provider,consumer 像调用本地方法一样调用远端方法。这个理解不能说错,但它太停留在使用层了。
真正让我开始修正这个理解的,是在读 ReferenceConfig.ReferregistryProtocol.ReferRegistryDirectory.List 时,我第一次意识到:consumer 最后拿到的不是一个“远端连接”,而是一整套会随注册中心、路由规则、重试策略、协议配置不断变化的治理视图。
这也是为什么我后来接触 Triple 方法路由兼容问题时,会本能地去想:“这个问题到底属于 metadata、service model,还是 router / transport 层?”因为我已经不再把 Dubbo 看成一个简单的远程调用工具,而是在尝试用运行时分层去理解它。
我现在对 Dubbo 的核心判断是:
Dubbo 的重点不仅是把 RPC 调通,还在于把一次调用放进可发现、可路由、可重试、可观测、可扩展的服务治理链路里。RPC 是入口,治理才是主线。

我一开始误解了什么

我一开始脑子里的 Dubbo 大概是这样:
这个模型能解释“为什么我能像调用本地方法一样调远端服务”,但它解释不了 Dubbo 里更重要的部分:
  • provider 地址从哪里来;
  • 一个接口为什么可能对应多个应用、多个实例、多个协议;
  • 同一个方法为什么可以有自己的 timeout、retry、loadbalance;
  • 路由规则、标签、限流、鉴权、tracing 在调用链路里处在哪一层;
  • 跨语言调用里,方法名、序列化、metadata 不一致时,兼容逻辑应该放在哪里。
后来我发现,如果用“远程连接”理解 Dubbo,很多代码都会显得绕;但如果用“治理链路”理解它,很多层次就变得合理了。

为什么我敢把“治理才是主线”说得这么重

对我来说,它来自 dubbo-go 里的三个连续证据。
第一,ReferenceConfig.Refer 并不是直接创建到 provider 的连接,而是先构造 interface-level URL,把接口名、协议、metadata type、method 参数等信息组织成服务坐标和引用配置。入口在 config/reference_config.go:172。
第二,registryProtocol.Refer 最后返回的不是单个 provider invoker,而是 cluster.Join(directory):
这段代码说明:consumer 侧的 registry refer 把 Directory 交给 Cluster,返回一个治理入口。Directory 负责动态 provider 视图,Cluster 负责失败语义,consumer 看到的只是一个 invoker,但这个 invoker 背后已经挂上了治理链路。
第三,真正调用时还会进入 Directory.List -> RouterChain -> Cluster -> LoadBalance。也就是说,请求发到协议层之前,大部分关键决策已经发生:当前有哪些 provider、哪些要被路由掉、失败后是否重试、最终选哪个节点。
所以我说“RPC 是入口,治理才是主线”,在 dubbo-go 的调用路径里,RPC 发送只是最后一步;在它之前,框架已经完成了一轮服务坐标解析、动态服务发现、路由筛选和失败语义决策。

consumer 拿到的不只是连接,还是治理视图

我现在理解的 dubbo-go 调用链是这样的:连接不是核心,治理视图才是核心。
这张图里我关注的不是最后的 RPC,而是 cluster.Join(directory) 之后形成的治理入口。consumer 看起来只是拿到了一个 invoker,但这个 invoker 背后挂着动态服务视图、路由链、负载均衡和失败策略。
Invoker 定义:
这个接口很小,但它把整条链路统一成一种形状:不管背后是本地 proxy、filter wrapper、cluster invoker,还是 Triple / Dubbo 协议 invoker,最终都可以被看成一个可以执行 Invoke(ctx, invocation) 的对象。
这对我理解 Dubbo 很关键。业务看到的是“调用某个接口方法”;Dubbo 内部看到的是“一份携带方法、参数、上下文、治理参数的调用描述,经过一组 invoker 逐层处理”。
Invocation 不只是方法名和参数,它还包含参数类型、attachments、attributes、generic invocation、context 传播等信息。
这说明调用在进入协议层之前,已经被框架改造成了一个可被治理链路处理的对象。

Directory 不仅是地址缓存,还是动态服务视图

Directory 定义:
这里可以看出:Directory 不是静态地址缓存,而是 consumer 本地的一份动态服务视图。
List 会走 router chain:
Route 会继续遍历 router:
这两段代码都说明:consumer 每次调用拿到的候选 invokers,并不是简单从 map 里取出来,而是会经过当前路由链过滤。注册中心事件、配置中心规则、路由规则、provider 上下线,最终都会影响它给调用链路返回的候选 provider。
真正调用时,failover cluster 会先 Directory.List(invocation),再读取 retries、loadbalance,并在失败重试前重新 List
这个细节可以说明:Dubbo 的重试不是对同一个地址机械重发,而是重新进入一次当前治理视图。
因为 provider 列表可能已经变化,路由规则可能已经变化,某个 invoker 的可用性也可能变化。重试如果不重新看 Directory,就会错过这些运行时变化。

metadata 不仅是附属信息,也是服务发现缺口的补全

理解 Directory 之后,我又遇到另一个问题:如果是 application-level service discovery,注册中心发现到的是应用实例,那 consumer 怎么知道这个应用实例到底提供了哪些接口?
如果只有实例地址,consumer 最多知道“某个应用实例活着”。但在 application-level discovery 里,这还不够。consumer 还需要知道:这个实例暴露了哪些接口、哪些协议、哪些方法、group/version 是什么、序列化参数是什么。否则它无法把“应用实例”恢复成真正可调用的 invoker 列表。
上面说的这些就是 metadata 的位置。
源码的注释说得很直接:application-level registry 基于 ServiceDiscoveryServiceNameMapping 和 metadata。为了兼容 interface-level registry,可以理解成:
我现在理解的 metadata 作用,是补上“实例 -> 服务能力 -> 可调用 URL”的桥。
MetadataInfo 是 application-level discovery 下,把实例恢复为接口级可调用信息的关键载体。
MetadataInfo 定义:
所以后面 ServiceInstance.ToURLs 那一步才成立。它把实例地址和 ServiceInfo 拼回 Dubbo 能调用的 URL。具体实现如下:
我的理解:metadata 是 application-level discovery 能不能还原服务能力的关键机制。
当然,这也有代价。metadata 引入了 revision(版本)、缓存、metadata report / metadata RPC、跨版本兼容等复杂度。 GetMetadataFromRpc 就需要根据 metadata service URL、Triple V1/V2、旧版本返回格式来恢复 metadata。
所以 metadata 的 trade-off 是:它让 application-level discovery 具备表达服务能力的能力,但代价是 consumer 侧要处理更多运行时状态和兼容路径。

Triple 路由让我理解了分层边界感

如果说 Directory / Router / Cluster 让我看到 Dubbo 如何治理一次调用,那么 Triple 路由让我看到另一个问题:当兼容性冲突出现时,治理运行时必须判断这个变化应该落在哪一层。
我就提交过一个路由相关的PR:fix(triple): case-insensitive method routing without metadata pollution (#3277)。问题大概是:Triple 方法路由时,方法名首字母大小写可能不一致。比如 Go 侧(服务端)注册 SayHello,而Java 侧(消费端)请求 sayHello
一种做法是把两种方法名都写进 metadata 或 service model。但这会污染服务描述:服务本来只有一个方法,却因为传输兼容被描述成两个方法。
我在路由层把问题收住了。兼容逻辑放在的 methodRouteMux
同一个兼容问题,落层不同,代价不同:
这个实现说明:跨语言方法名兼容属于 transport routing 层的问题,不应该轻易上升到 metadata / service model。
它的策略是:
  • 先走精确匹配;
  • 精确匹配失败后,只对 path 最后一段方法名做首字母 fallback;
  • service path 仍然保持大小写敏感;
  • fallback key 冲突时保留第一次注册,保证行为确定;
  • 不把兼容逻辑扩散到 metadata 和 ServiceInfo。
我本地跑过相关测试:
输出:
这也让我更确信,Dubbo 的复杂度并不只是因为它功能多,而是因为它必须持续回答“这类问题到底属于哪一层”。这是服务治理运行时和普通业务系统很不一样的地方:业务系统更关心功能是否完成,中间件还要关心功能完成后有没有污染契约、破坏边界、制造下一层的不确定性。

Dubbo 的分层是在隔离变化

现在回头看 Dubbo 的这些抽象,我会把它们理解成对不同变化的隔离:
抽象
处理的问题
我的理解
URL
服务坐标和治理参数
不只是地址,而是 interface、group、version、protocol、method config 的组合
Invocation
单次调用描述
把方法、参数、上下文、attachments 变成可被治理链路处理的对象
Invoker
统一执行接口
让 proxy、cluster、filter、protocol 都能用同一种调用形状串起来
Directory
动态服务视图
把注册中心和配置变化转换成当前可调用 provider 列表
RouterChain
调用前筛选
根据标签、规则、服务坐标筛掉不该调用的 provider
Cluster
失败语义
决定 failover、failfast、failsafe 等行为
LoadBalance
节点选择
在候选 provider 里选一个具体 invoker
Filter
横切治理
鉴权、限流、metrics、tracing 等能力不侵入业务方法
Protocol
传输落地
Triple、Dubbo、REST 等真正负责网络协议和序列化
这张表对应我现在理解 Dubbo 的方式:不同层负责吸收不同类型的变化。
provider 上下线,主要影响 Directory;路由规则变化,主要影响 RouterChain;失败策略变化,主要影响 Cluster;横切能力变化,主要影响 Filter;跨语言传输和协议兼容,应该尽量留在 Protocol 层。
这条链路带来的代价也很明确。
第一,URL 变重,因为它不只是地址,还承载服务坐标和治理参数。
第二,Directory 是动态视图,所以 consumer 看到的是最终一致的 provider 列表。
第三,metadata 补上了 application-level discovery 的服务能力恢复,但引入 revision、缓存和远程拉取。
第四,协议层要处理跨语言兼容,但必须避免把传输兼容污染到服务模型。
这些代价是服务治理运行时为了隔离变化付出的成本。

再次叠个甲

这篇文章说的是:
从 dubbo-go 当前代码看,Dubbo 的一次调用不是 consumer 直接连 provider,而是经过 Invocation、Invoker、Directory、RouterChain、Cluster、LoadBalance、Filter、Protocol 这些层共同组织。
也说明了:metadata 在 application-level service discovery 里不是附属信息,而是从应用实例恢复服务能力的关键机制。
我还用 Triple method route mux 这个我的PR说明:中间件里的兼容性问题,应该尽量放在正确层解决,避免污染更高层的服务模型。
当然,我现在的理解仍然不完整,还有许多逻辑没能吃透。
但至少这次我抓住了一条主线:Dubbo 的核心不是 RPC API,而是围绕调用建立治理链路。

我的理解升级

对我来说,这篇文章里的最重要的收获,不是“我理解了 Dubbo 的架构”,而是我第一次开始用治理的视角去看这个中间件
以前我更关心调用能不能发出去:provider 起了吗,consumer 配了吗,协议通了吗。
现在我更关心:
  • 调用是怎么被描述的;
  • 服务能力是怎么被发现和还原的;
  • provider 列表是怎么被路由规则筛选的;
  • 失败语义是在哪一层决定的;
  • metadata、协议和跨语言兼容的边界在哪里;
  • 可观测、限流、鉴权这些能力如何不侵入业务方法。
当一次调用进入分布式系统之后,基础设施如何决定它应该去哪里、怎么去、失败后怎么办,以及如何被观察和控制。
我现在对 Dubbo 的一句话理解是:
Dubbo 把一次远程调用抽象成可发现、可路由、可重试、可观测、可扩展的治理对象;RPC 只是这条链路最后落到网络上的动作。
回到首页