Skip to content

Apache Dubbo:一次 RPC 调用如何穿过代理、集群与网络

Apache Dubbo 是面向 Java 生态的 RPC 与微服务框架。框架把应用配置、服务暴露、注册发现、集群决策、协议传输、序列化、治理扩展和观测能力组织成可组合的模块,让业务接口保持稳定,同时支持多种通信协议与服务治理方式。

从整体结构看,配置与生命周期模块负责启动和装配,注册中心模块维护服务地址目录,Cluster 模块完成路由、负载均衡和容错,RPC 与 Remoting 模块负责 Invocation、编码、请求响应和超时,Dubbo SPI 则把协议、过滤器和其他扩展点连接起来。框架的关键外部依赖包括注册中心、网络传输、序列化实现和应用运行时。

源码中最重要的抽象是 Invoker:服务实现会被包装成可调用对象,远端地址也会被包装成可调用对象;代理、集群、过滤器和协议层围绕同一套 invoke 契约组合。本文以仓库的 dubbo-demo-api 为入口,重点走读 ZooKeeper 注册发现、Dubbo TCP 协议和同步 sayHello 调用;Triple、REST、Spring 注解、IDL、服务网格和控制面只建立职责地图,不混入同一条已核对传输链。

GitHub 仓库信息

信息内容
项目标题Apache Dubbo
项目描述Apache Dubbo 的 Java 实现,一套 RPC 与微服务框架。
GitHub 仓库apache/dubbo
官网地址https://dubbo.apache.org/
主要开发语言Java 99.36%,另含 Groovy 0.33% 和 Mustache 0.20%
开源许可证Apache-2.0
最近代码更新2026-08-27(GitHub pushed_at,核对日期:2026-09-01)

仓库结构与模块职责

Dubbo 是 Maven 多模块仓库。源码解析先按运行时职责分组,读者可以从下面的骨架定位一次 RPC 所需的模块,再进入具体类和调用链。

text
dubbo/
├── dubbo-common/       URL、SPI、配置工具与公共模型
├── dubbo-config/       Bootstrap、服务导出与引用生命周期
├── dubbo-registry/     注册中心适配与 RegistryDirectory
├── dubbo-cluster/      Router、LoadBalance、Cluster 与容错
├── dubbo-rpc/          Invoker、代理与 Dubbo/Triple 等协议
├── dubbo-remoting/     Channel、Exchange、Future 与编解码
├── dubbo-serialization/序列化扩展
├── dubbo-demo/         provider、consumer 与协议示例
├── dubbo-plugin/       Triple Servlet 等可选接入
└── pom.xml             多模块聚合与版本管理
语义模块主要职责阅读入口
配置与生命周期把应用配置转成服务 export/refer 的启动动作DubboBootstrapDefaultApplicationDeployerDefaultModuleDeployer
注册与目录注册 provider、订阅地址变化并维护 Invoker 集合RegistryProtocolRegistryDirectory
集群决策路由、负载均衡、容错和重试AbstractClusterInvokerFailoverClusterInvoker
RPC 与协议代理、Invocation、协议导出和引用InvokerInvocationHandlerDubboProtocol
网络交换请求 ID、响应 Future、超时和连接HeaderExchangeChannelDefaultFuture
扩展基础设施通过 URL 和 SPI 选择实现并组合 Wrapper/FilterExtensionLoaderAdaptiveClassCodeGenerator

核心能力、实现机制与设计思想

Dubbo 的核心能力可以归纳为四个相互咬合的机制:

  • 服务发现把注册中心的地址通知转换成动态 Directory,业务代理因此不必随实例变化而重建;
  • 集群层在候选 Invoker 上执行路由、负载均衡和容错,把“调用哪台机器”从业务接口中抽离;
  • 协议与 Remoting 层把 Invocation 编码成请求,并用 Future 统一处理同步返回、异步完成和超时;
  • Dubbo SPI 通过 @Adaptive、Wrapper、Filter 和 @Activate 支持协议、治理和扩展点的运行时替换。

这些能力的共同设计是围绕 Invoker 组合,而不是让每个模块互相调用具体实现。收益是扩展边界清楚;代价是调用栈较深,排障需要先确认当前 Invoker 所在层次。

源码环境准备与本地运行

仓库中的基础 demo 定义了一个 DemoService 接口。提供方把服务地址与接口信息注册到 ZooKeeper,DemoServiceImpl 对象仍留在 provider 进程内;消费方从注册中心取得代理,再执行 sayHello("dubbo")。为了让这次 Hello 调用发生,启动阶段先完成配置装配、服务暴露和注册订阅;请求阶段再经过代理、路由、负载均衡、协议和网络交换,正好可以串起 RPC 主干。

准备与运行

源码构建目标是 Java 8,固定提交的 CI 同时覆盖 Java 8、11、17、21 和 25。为了减少本地工具兼容变量,可优先使用 JDK 17 或 21、仓库自带的 Maven Wrapper,以及监听 127.0.0.1:2181 的 ZooKeeper。下面先固定到本文分析的提交:

bash
git clone https://github.com/apache/dubbo.git
cd dubbo
git checkout 5553cb77bd26b717c8cbaf83079765ac05d7d164

在仓库根目录先编译源码和示例模块,跳过测试可以缩短首次构建时间:

bash
./mvnw -DskipTests install

成功信号是 Maven 输出 BUILD SUCCESS;需要验证测试时再执行 ./mvnw test,不要把跳过测试的安装结果写成测试通过。

在 IDE 中以 Maven 项目导入仓库,依次运行:

  1. dubbo-demo-api-provider 中的 org.apache.dubbo.demo.provider.Application
  2. dubbo-demo-api-consumer 中的 org.apache.dubbo.demo.consumer.Application

提供方的启动类明确配置 DUBBO 协议和 ZooKeeper,消费方的启动类在 Bootstrap 启动后从缓存取得引用并调用接口。成功时,消费方日志应出现类似内容:

text
Hello dubbo, response from provider: <provider-local-address>

提供方日志会同时记录 request from consumer 和消费方地址。这些成功信号来自示例实现;本次分析没有启动 ZooKeeper、编译仓库或实际运行 demo。

若启动即报注册中心连接错误,先确认 127.0.0.1:2181 可达。若消费方提示 No provider available,检查提供方是否已完成注册,以及接口、group、version 是否一致。提供方已经监听但请求报 Not found exported service 时,优先核对请求携带的 path、group、version 与导出服务键,而不是先怀疑业务实现。

消费方示例还配置了一个本地 TRIPLE 协议端口,但 ReferenceConfig 本身没有限定消费协议。注册目录会根据引用接受的协议和提供方地址计算有效协议;当前提供方发布的是 dubbo:// 地址,因此本文追踪 DubboProtocol。这也是读 demo 时容易误判的一处:应用持有某个 ProtocolConfig,不等于每个远端引用都会强制使用该协议。

整体架构地图与功能分布

只看这次调用涉及的职责,仓库可以收束成下图。箭头表达运行时交接,不表示 Maven 依赖的全部方向。

语义角色关键模块本文关心的问题
应用装配dubbo-config/dubbo-config-apiBootstrap 怎样初始化,服务何时 export,引用何时 refer
地址变化dubbo-registry/dubbo-registry-api注册中心通知怎样变成可调用的 provider 列表
调用决策dubbo-cluster路由后如何负载均衡,失败后怎样重选实例
RPC 核心dubbo-rpc-apidubbo-rpc-dubboJava 代理怎样生成 Invocation,协议怎样把 Invocation 发出去
网络交换dubbo-remoting-apidubbo-remoting-netty4请求 ID、响应 Future、超时和连接由谁管理
扩展机制dubbo-common为什么同一调用链可以换协议、注册中心、负载均衡和过滤器

dubbo-rpc-tripledubbo-plugin/dubbo-triple-servlet 是另一条 HTTP/2/Servlet 传输实现;dubbo-config-springdubbo-spring-boot 负责把 Spring 配置转成相同的底层模型。读核心调用链时先绕开这些适配层,能少追很多并不改变 RPC 主干的生命周期回调。

程序入口、初始化与启动链路

DubboBootstrap.start() 本身很薄,只把启动交给 ApplicationDeployer,并默认等待 Future 完成。真正的顺序分成应用级和模块级两层。

应用级初始化流程先注册关闭钩子,启动配置中心,加载应用配置,初始化模块 Deployer、指标、观测注册表和元数据中心。这里有一个实用含义:demo 同时把 ZooKeeper 配成配置中心、注册中心和元数据中心,start() 期间任一角色不可达,都可能在业务调用之前暴露问题。

模块级启动流程按以下顺序推进:

text
initialize module
  -> exportServices
  -> prepare internal module
  -> referServices
  -> wait async export/refer if needed
  -> registerServices
  -> checkReferences
  -> module completion

先 export、后 refer,使同一应用内同时存在 provider 和 consumer 时,本地服务有机会先就绪;异步 export/refer 则由 Future 汇合后再完成注册和引用检查。启动失败时,模块 Deployer 会尝试 unexport 已暴露的服务,再把状态切到 failed,避免留下看似可用的半启动模块。

提供方:把实现对象变成可寻址服务

demo 只写了 service.setInterface(...)service.setRef(new DemoServiceImpl())。框架随后完成三次形态转换。

第一步,ServiceConfig校验配置并为每个协议生成服务 URL。URL 在 Dubbo 中不只是网络地址,还携带 interface、group、version、timeout、serialization 等运行参数。

第二步,服务导出末端用 ProxyFactory 把 DemoServiceImpl 包成 provider Invoker,再调用自适应 Protocol.export。有注册中心时,外层 RegistryProtocol 负责本地导出、provider 注册和配置订阅;真正的 dubbo:// URL 仍会被交给 Dubbo 协议实现。

第三步,DubboProtocol.export按 service key 把 Exporter 放进映射,并打开协议 Server。service key 是由接口、端口、group、version 等信息组成的服务定位键;Exporter 则持有 provider Invoker 和取消暴露能力。请求到达后,框架重新计算服务键并从映射中找到原始 provider Invoker。因而“端口已监听”只证明网络入口存在,并不证明某个服务键已经正确导出。

消费方:代理背后是一棵 Invoker

ReferenceConfig.get() 不会直接连接某台 provider。ReferenceConfig 先确保模块启动,再初始化 ConsumerModel,并在 createProxy 中完成地址和代理装配。

注册中心 URL 会先进入 RegistryProtocol.refer。Directory 可以理解为 consumer 眼中的实时 provider 目录。经典接口级发现路径会创建动态 Directory,注册 consumer,建立 RouterChain,订阅 provider 地址,再用 Cluster 包住 Directory。Dubbo 3 的迁移 Invoker 还可能在接口级发现与应用级服务发现之间切换;因此调试 3.x 地址问题时,需要先确认当前迁移规则选中了哪类 Directory。

注册中心通知到达后,RegistryDirectory.notify把 URL 分为 configurator、router 和 provider,再刷新地址。旧 URL 可以复用已有 Invoker;新增或参数变化的 URL 会在 toInvokers 中经过协议匹配,调用对应 Protocol.refer 生成远端 Invoker。

最后,ProxyFactory 把整棵 Invoker 包成 DemoService。业务代码拿到的是接口代理,代理内部却保留了“动态地址目录 -> 路由 -> 负载均衡 -> 容错 -> 过滤器 -> 协议 Invoker”的组合结构。地址变化只需更新 Directory,不必重新生成业务接口代理。

核心功能链路矩阵

链路入口关键模块输出或副作用
启动与暴露DubboBootstrap.start()config、registry、rpcprovider 导出并注册地址
引用与调用ReferenceConfig.get()、代理方法registry、cluster、rpc、remotingconsumer 收到返回值或异常
超时与重试DefaultFutureFailoverClusterInvokerremoting、cluster超时分类、重选 provider 或终止
扩展选择Protocol.export/refer、Filter 构建common、rpc、cluster按 URL 装配协议与过滤器链

调用:sayHello 如何得到响应

下面的链路以一次双向、同步的 Dubbo TCP 调用为例。同步是业务接口看到的形态,协议核心从发出请求开始已经使用 Future 表达异步结果。

  1. InvokerInvocationHandler拦截接口方法,记录方法名、接口名、参数类型和参数,生成 RpcInvocationObject 自带方法和 $destroy 在这里被提前处理,不进入远程调用。
  2. AbstractClusterInvoker.invoke向 Directory 取候选 Invoker。RouterChain 已在 Directory 内参与筛选;候选为空时立即抛出带服务键和注册中心信息的 No provider available
  3. Cluster 根据方法级配置选择 LoadBalance,再进入具体容错策略。默认 failover 路径会选一个 provider;非业务异常触发下一轮时,FailoverClusterInvoker 会重新向 Directory 取列表,避免继续使用已经下线的旧快照。
  4. 选中的 DubboInvoker.doInvoke计算方法超时,选择 ExchangeClient。双向调用执行 request(invocation, timeout, executor),把返回的 CompletionFuture 转成 AsyncRpcResult
  5. HeaderExchangeChannel.request创建带 ID 的双向 Request,先登记 DefaultFuture 和超时任务,再向底层 Channel 发送。先登记再发送,避免响应很快返回却找不到等待者。
  6. provider 端 HeaderExchangeHandler检查解码状态,并把双向请求交给协议 handler。DubboProtocol 按 service key 找到 Exporter,设置远端地址,再调用 provider Invoker,最终进入 DemoServiceImpl.sayHello
  7. provider 的 CompletionStage 完成后,HeaderExchangeHandler 以同一请求 ID 发回 Response。handler Future 异常会映射为 SERVICE_ERROR;普通业务异常通常保留在 Result/AppResponse 中交给 consumer 重建。解码失败则在进入业务 Invoker 前返回 BAD_REQUEST。
  8. consumer 收到 Response 后,DefaultFuture.received按 ID 移除等待项、取消超时任务并完成 Future。同步接口在代理边界等待结果并重建返回值或异常,业务代码最终得到 String。

这条链有两个有用的认知转折。第一,注册中心不转发业务请求,只向 Directory 提供和更新地址;真正的数据流从 consumer 直达 provider。第二,同步 RPC 并不要求网络层阻塞等待,Dubbo 用异步 Future 统一响应,再由代理边界决定调用者看到同步值还是 CompletableFuture。流式调用由 Triple 等并行协议路径实现,不属于本文已经证明的 Dubbo TCP + DefaultFuture 链。

分支、失败、降级与扩展链路

超时判断位于 Remoting 层。DefaultFuture 在创建时进入请求 ID 映射并注册定时任务;超时触发时,会根据请求是否已经标记为 sent 区分 CLIENT_TIMEOUTSERVER_TIMEOUT。响应晚于超时到达时,等待项已经移除,框架只记录迟到响应,不会再次完成原调用。固定提交中的 DefaultFutureTest覆盖了已发送请求的超时异常。

重试由 Cluster 层决定。FailoverClusterInvoker把总尝试次数计算为 retries + 1,每轮选择 provider,并在重试前重新列出候选地址。业务异常直接抛出,不进入 failover;网络或超时类 RpcException 才可能推动下一次尝试。相应测试确认 retries=0 只调用一次,也确认 provider 列表在第一次失败后变化时会重新选择最新 Invoker。

两层分开带来清晰职责,也带来一个业务成本:consumer 超时并不证明 provider 没有执行成功。若第一次请求已到达 provider,只是响应丢失或过晚,重试可能再次执行写操作。Dubbo 能选择另一台机器,不能替业务自动生成幂等语义;订单扣款、库存修改、消息发布等操作仍要使用业务唯一键、去重记录或状态机。

扩展:URL 驱动的 SPI 与过滤器链

Protocol 接口上的 exportrefer 标记了 @Adaptive。Dubbo 的扩展加载器会为这类接口生成自适应实现,从 URL 的 protocol 或参数中取得扩展名,再选择 dubboregistrytri 等实现。固定提交的资源文件把 dubbo 映射到 DubboProtocol,把 registry 映射到兼容注册协议;AdaptiveClassCodeGenerator则生成 URL 检查、扩展名解析、ExtensionLoader 获取和目标方法调用代码。

这套设计让协议选择发生在同一个 Protocol 调用点,而不需要到处写 if (dubbo) ... else if (tri) ...。代价是运行时对象常被 Adaptive、Wrapper、Listener 和 Filter 多层包装,调试时看到的 Invoker 类型未必是最终实现。

过滤器也采用组合方式。DefaultFilterChainBuilder按 URL 与激活条件取得 Filter,再逆序包成 Invoker 链。鉴权、上下文、指标和追踪可以进入调用路径,而不改协议实现。新增 Filter 时需要同时考虑 consumer/provider 分组、激活条件、顺序和异步回调;只实现 invoke 而忽略结果完成阶段,容易得到请求前逻辑正常、响应后清理缺失的扩展。

核心思想、可学习设计与不足

一套 Invoker 契约降低组合成本

代理、Cluster、Filter 和 Protocol 都围绕 Invoker 组合,使本地实现、远端节点和多节点集群可以使用相同调用形状。扩展层之间因此相对解耦。成本是调用栈很深,排障不能只按类名搜索;更有效的方法是先判断当前拿到的是 provider Invoker、protocol Invoker 还是 cluster Invoker。

动态 Directory 隔离地址变化

业务代理保持稳定,RegistryDirectory 增量替换 provider Invoker。注册中心抖动、配置覆盖和路由变化不会要求业务重新注入接口。相应成本是“地址存在”“地址通过路由”“Invoker 可用”是三种不同状态,看到 ZooKeeper 节点并不能直接证明请求拥有候选 provider。

异步核心兼容多种接口形态

网络请求、provider 返回和 consumer 响应都用 CompletionStage/Future 衔接,同一核心可以支持同步值和 CompletableFuture 两种上层方法形态。同步调用仍会在代理边界等待;高并发下,线程模型、回调执行器和超时配置仍会直接影响资源占用。Triple 的流式 API 需要沿独立协议实现继续分析,不能从当前 Future 链直接推出。

修改与排障入口

目标从哪里进入继续核对什么
新增协议Protocol、对应 dubbo-rpc-* 模块、SPI 映射文件export/refer 对称性、端口生命周期、编解码、Future 与错误映射
新增负载均衡LoadBalancedubbo-cluster/loadbalance方法级参数、预热权重、粘滞调用、候选为空和统计状态
新增调用过滤器FilterClusterFilterDefaultFilterChainBuilderconsumer/provider 激活组、顺序、异步完成和资源清理
排查无 providerRegistryProtocol、迁移 Invoker、RegistryDirectory.notify注册模式、订阅类别、协议匹配、路由结果和可用性
排查超时DubboInvokerHeaderExchangeChannelDefaultFuture超时值来源、是否 sent、迟到响应、线程池和 provider 耗时
排查错误重试FailoverClusterInvoker 和方法配置异常是否为业务异常、总尝试次数、是否重选地址、业务幂等
阅读 Tripledubbo-rpc-triple、IDL demo、Triple servlet 插件HTTP/2 流、序列化、Unary/Streaming 分流,不套用 Dubbo TCP 细节

若准备给 Dubbo 本身提交修改,最小验证不应只覆盖目标类。改注册目录要同时看地址变更与销毁测试;改 Failover 要覆盖业务异常、网络异常、重试次数和重选;改 Remoting Future 要覆盖正常响应、发送前失败、发送后超时、迟到响应与执行器关闭。仓库 CI 的完整矩阵成本较高,适合先运行精确模块测试,再进入全仓检查。

应用场景与同类产品对比

Dubbo 适合需要 Java 服务治理、动态发现、方法级路由与容错,并希望协议和治理能力可扩展的团队。只有少量内部 HTTP 接口时,引入 Dubbo 会增加第二套服务模型和排障链路;跨语言以严格 IDL 为主时,应比较原生 gRPC;希望把路由和 mTLS 下沉到基础设施时,则应比较服务网格。

方案强项代价更适合
DubboJava RPC、注册发现、路由、容错和 SPI 集成运行时抽象层多,跨语言和安全边界需额外设计Java 微服务与服务治理
gRPC跨语言 IDL、HTTP/2 生态和明确的接口契约服务发现、治理和重试通常要组合其他组件跨语言 RPC 与 API 契约
Spring Cloud HTTP与 HTTP 网关和 Web 生态衔接直接高性能 RPC、方法级路由与协议扩展需另配HTTP API 为主的团队
服务网格将部分流量治理下沉到基础设施应用语义、幂等和调试链路仍需业务配合平台化多语言服务治理

构建、安装、部署与调试问题

Dubbo 的源码构建、ZooKeeper、provider/consumer 启动和超时排查已经在前文与“修改与排障入口”中展开;这里按现象给出最短定位顺序,便于从源码入口回到运行问题。

运行前置与常见卡点

现象优先检查成功信号
ZooKeeper 连接失败127.0.0.1:2181 可达、注册中心配置和启动顺序provider 完成注册,consumer 不再报连接异常
No provider availableRegistryDirectory.notify 的 provider URL、group/version 与路由结果Directory 列表包含可用 Invoker
Not found exported serviceprovider 的 service key 与请求 path、group、versionDubboProtocol 找到对应 Exporter
请求超时DubboInvoker 超时值、DefaultFuture 的 sent 状态和 provider 耗时能区分客户端超时、服务端超时与迟到响应

总结与后续阅读路径

Dubbo 把一次远程调用拆成可替换的代理、目录、集群、协议和网络层。继续阅读时,可以从 RegistryDirectory 追地址变化,从 FailoverClusterInvoker 追容错,从 DefaultFuture 追超时响应,从 ExtensionLoader 追 SPI 扩展。

参考资料

文章结论绑定 source commit;动态信息以核对日期为准。