Dubbo2 巩固教程之三
大纲
- Dubbo 2 巩固教程之一、Dubbo 2 巩固教程之二、Dubbo 2 巩固教程之三
- Dubbo 2 巩固教程之四、Dubbo 2 巩固教程之五、Dubbo 2 巩固教程之六
- Dubbo 2 巩固教程之七
前言
学习资源
Dubbo 深入理解
服务治理的源码剖析
版本说明
| 组件 | 版本 | 说明 |
|---|---|---|
| Dubbo | 2.6.6 | 在阅读 Dubbo 源码时要注意,不同版本由于协议升级、SPI 改动、注册中心增强等因素,底层实现会出现细微差异;版本跨度越大,差异通常越明显。 |
基础概念
使用 Dubbo 开发时,可能会遇到以下问题:
- (1) 服务接口粒度较粗时,如何实现读写场景的分离?
- (2) 查询接口返回列表时,如何区分单条查询与多条查询的调用场景?
- (3) 运行过程中,如果需要为某个应用额外增加服务实例,该如何实现?
- (4) 运行过程中,如果需要动态调整服务权重,该如何实现?
- (5) 运行过程中,如何需要动态调整服务的负载均衡策略,该如何实现?
- (6) 如何在不停服的情况下发布新的服务版本?
Dubbo 的服务治理主要用于在运行时动态调整服务的行为和调用选址策略,从而实现限流、权重调整、路由控制等能力。

Dubbo 的服务治理(包括条件路由、标签路由、黑白名单、动态配置、权重调整、负载均衡等),都支持通过 Dubbo-Admin 服务治理控制台进行配置(动态调整),如下图所示:

思考问题
- Dubbo 如何实现灰度发布、蓝绿发布、红黑发布、滚动发布、金丝雀发布、A/B 测试呢?
- Dubbo 本身是服务治理框架,不负责流量调度,但它提供了条件路由 (ConditionRouter)、标签路由(TagRouter)、动态配置等机制,使得可以基于这些能力实现各种灰度与流量控制策略。
服务暴露
服务暴露的流程
1、读取配置
- Spring 启动 → 解析
@DubboService/<dubbo:service>→ 生成 ServiceBean。
- Spring 启动 → 解析
2、初始化 ServiceBean
- ServiceBean 触发
afterPropertiesSet()→ 准备接口、实现类、协议、端口等信息。
- ServiceBean 触发
3、导出服务(Export)
- 调用
ServiceConfig.export()→ 进入真正的暴露逻辑。
- 调用
4、本地导出服务(可选)
- 如果 Provider 配置了
scope=local(或旧版本的写法scope=injvm) → 导出服务到本地 JVM,通过InjvmProtocol将服务放入本地内存 Map。
- 如果 Provider 配置了
5、远程导出服务
- 走
doExportUrls()→ 选择协议(如dubbo) - 调用
DubboProtocol.export()→ 创建 Exporter 并打开 Netty Server 监听端口。
- 走
6、注册中心注册服务
- 如果有 Registry(比如 ZooKeeper) → 调用
ZookeeperRegistry.register()→ 将 URL 节点写入/dubbo/${serviceInterface}/providers/。
- 如果有 Registry(比如 ZooKeeper) → 调用
7、启动完成,写入元数据(Metadata)
MetadataService写入元数据 → 用于服务治理和服务消费者订阅。

服务提供者的配置
- Dubbo 服务提供者的配置信息如下图所示:

服务暴露的入口方法
- Dubbo 服务暴露的入口方法是
ServiceConfig.export(),可以从这个方法入手阅读 Dubbo 服务暴露的底层源码实现
1 | import org.apache.dubbo.config.ServiceConfig; |
服务暴露的 URL 组装
Dubbo 负责组装并导出服务 URL 的方法是
ServiceConfig.doExportUrlsFor1Protocol()(部分核心代码如下图所示)。该方法的主要作用:- 为单个协议(Protocol)生成并导出服务 URL(比如
dubbo://127.0.0.1:20880/com.demo.service.DemoService?anyhost=true...) - 完成服务的本地导出 + 远程导出逻辑
- 注册服务到注册中心(Registry)
- 处理协议相关的参数(比如:权重、
token、timeout)等配置
![]()
- 为单个协议(Protocol)生成并导出服务 URL(比如
Dubbo 中 URL 是一个非常重要的契约,所有扩展点都要遵守。
所有扩展点参数都包含 URL 参数,URL 作为上下文信息贯穿了整个扩展点设计体系。
URL 采用标准格式:
protocol://username:password@host:port/path?key1=value1&key2=value2...。- 比如:
dubbo://127.0.0.1:20880/com.demo.service.DemoService?anyhost=true&application=provider-application&dubbo=2.0.2&dynamic=true&generic=false&side=provider...
- 比如:
提示
Dubbo 将服务注册到 ZooKeper 注册中心后,ZooKeper 注册中心里面的数据存储结构和数据格式可以看 这里 的介绍。
多注册中心、多协议暴露服务
- Dubbo 暴露服务时,支持多注册中心、多协议,具体方法是(部分核心代码如下图所示):
ServiceConfig.doExportUrls()ServiceConfig.doExportUrlsFor1Protocol()

导出本地服务与远程服务
Exporter 导出本地服务
在 ServiceConfig.doExportUrlsFor1Protocol() 方法中,会调用 ServiceConfig.exportLocal() 方法将服务导出到本地,即导出服务到本地 JVM(injvm)。下图展示了 ServiceConfig.exportLocal() 方法的核心源码:

Exporter 导出远程服务
Dubbo 将服务导出到远程的流程较为复杂,包含 服务导出 和 服务注册 两个阶段。当循环遍历到协议头为 registry:// 的 registryURL 时,就会进入 RegistryProtocol.export() 方法进行处理。下图展示了 RegistryProtocol.export() 方法的核心源码:

绑定服务端口的过程
在 RegistryProtocol.doLocalExport() 方法中,会执行服务端口的绑定(即启动 TCP 服务器),下图展示了 RegistryProtocol.doLocalExport() 方法的核心源码:

当使用的是 dubbo 协议时,RegistryProtocol.doLocalExport() 方法会调用 DubboProtocol.export() 方法,后续的方法调用链是 DubboProtocol.openServer() -> DubboProtocol.createServer() -> Exchangers.bind() -> HeaderExchanger.bind() -> Transporters.bind() -> NettyTransporter.bind() -> NettyServer() -> AbstractServer() -> AbstractServer.doOpen() -> NettyServer.doOpen()

服务注册的过程
Dubbo 将服务注册到注册中心(比如 Zookeeper)的方法调用链是 RegistryProtocol.export() -> ZookeeperRegistry.register()。下图展示了 RegistryProtocol.export() 方法的核心源码:

服务暴露总结
Dubbo(2.7.x)服务暴露的时序图(源自 Dubbo 官方文档)如下所示:

Dubbo(2.6.6)服务暴露的完整调用链如下(仅供参考,并非绝对严谨):
1 | ServiceConfig.export() ─► 服务暴露入口 |
SPI 扩展机制
SPI 在服务暴露中的应用
在 Dubbo 服务暴露的源码中,难以理解的地方(以下源码位于
ServiceConfig.doExportUrlsFor1Protocol())1
2
3
4
5// Invoker、Exporter 是什么? 为什么 proxyFactory、protocol 能创造它们?
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);Invoker 与 Exporter
Invoker是实体域,作为 Dubbo 的核心模型,其他模型都向它靠拢或转换成它。它代表一个可执行体,能够将所有需要代理执行的方法,用Invoker进行抽象和转换。Exporter是一个服务暴露控制接口,用于获取暴露的Invoker,以及注销暴露的Invoker。
API 与 SPI
- Dubbo 框架有两类用户:框架使用者和框架扩展开发者。
- 为了对这两类用户进行隔离,使用者通过 API 来使用框架,开发者通过 SPI 来对框架进行扩展。
- 使用者无需从编码层面知道扩展类型,只需配置即可;扩展者也能很方便地扩展功能,不需要考虑适应不同的业务代码场景。
- Dubbo 没有采用 JDK 内置的 SPI 机制,而是自己实现了一套 SPI 机制。
SPI 自适应扩展点的介绍
- Dubbo SPI 自适应扩展的介绍
- Dubbo SPI 自适应扩展的作用是:在运行时根据参数值动态决定采用哪个扩展点实现类。
![]()
- Dubbo 中有多种类型的扩展点,例如协议、集群容错、负载均衡、注册中心、序列化等。这些扩展点在运行时获取参数的方式并不统一:有的依赖 URL 参数名获取参数值,有的在没有 URL 信息时只能通过
Invocation获取方法级参数。 - 为了解决不同扩展点参数来源不一致的问题
- Dubbo 通过 Adaptive(自适应)动态代码生成机制自动生成代理类,比如 Dubbo 自动生成的
Protocol$Adaptive代理类的代码示例可以看 这里; - Dubbo 自动生成的代理类,可以在运行时根据实际上下文(如 URL、
Invocation、ReferenceConfig、ServiceConfig等)灵活选择具体扩展实现,从而屏蔽参数来源差异,保证扩展点能够自适应地工作。
- Dubbo 通过 Adaptive(自适应)动态代码生成机制自动生成代理类,比如 Dubbo 自动生成的
- Dubbo SPI 自适应扩展的作用是:在运行时根据参数值动态决定采用哪个扩展点实现类。
- Dubbo SPI 自适应扩展的使用
- 在需要进行自适应的扩展类或接口方法标注
@Adaptive注解。 - 当
@Adaptive注解在接口方法上(最常见)- Dubbo 会为该接口方法生成代理逻辑,实现运行时根据参数动态选择具体扩展实现。
- 当
@Adaptive注解在扩展类上(较少使用)- Dubbo 不会为该扩展类生成代理类;
- 这种情况在 Dubbo 中很少见,目前仅有两个扩展类标注了
@Adaptive,分别是AdaptiveCompiler和AdaptiveExtensionFactory,表示这些扩展类的加载逻辑由人工编码(静态编码)完成。
- 在需要进行自适应的扩展类或接口方法标注
- Dubbo SPI 自适应扩展的实现
![]()
SPI 机制的使用示例
Dubbo 内部使用 SPI 机制的源码案例
![]()
自定义 Dubbo SPI 扩展的基本步骤(例如,自定义协议扩展实现)
- (1) 提供 SPI 插件的扩展实现类
- 提供一个 Java 类实现
org.apache.dubbo.rpc.Protocol接口1
2
3
4
5
6
7
8
9package com.spi.demo;
import org.apache.dubbo.rpc.Protocol;
/**
* 实现自己的 Dubbo 协议
*/
public class CustomizedProtocol implements Protocol {
// ...
}
- 提供一个 Java 类实现
- (2) 在指定的文件里面配置扩展实现类
- 在应用的
resources/META-INF/dubbo/目录下创建org.apache.dubbo.rpc.Protocol文件,并在文件中添加如下配置内容:1
customized=com.spi.demo.CustomizedProtocol
- 文件名必须为 SPI 接口的全限定名,例如:
META-INF/dubbo/org.apache.dubbo.rpc.Protocol,对应你要扩展的接口。 - 文件内容必须为
key=value形式,其中key为扩展名(可自定义,建议添加特定前缀避免与 Dubbo 内置实现冲突),value为扩展实现类的全限定名。
- 在应用的
- (3) 通过 Dubbo 配置启用自定义协议实现
- 在应用中修改 Dubbo 的协议配置,告诉 Dubbo 框架使用自定义协议:
1
2
3
4# 使用 SpringBoot 时,可修改 application.yml 或 application.properties 配置文件
dubbo
protocol
name: customized - 或者,使用原生 API 告诉 Dubbo 框架使用自定义协议:
1
2ProtocolConfig protocol = new ProtocolConfig();
protocol.setName("cutomized");
- 在应用中修改 Dubbo 的协议配置,告诉 Dubbo 框架使用自定义协议:
- (1) 提供 SPI 插件的扩展实现类
SPI 机制的核心注解
Dubbo SPI 机制有两个核心注解,分别是 @Adaptive 和 @Activate。
@Adaptive注解(自适应)- 主要作用
- 表示该扩展类或接口方法需要自适应扩展,Dubbo 会在运行时根据 URL 或其他条件动态选择具体扩展实现。
- 常见使用方式
@Adaptive标注在接口方法上(最常见)- 表示由 Dubbo 自动生成
Xxx$Adaptive代理类 - 方法内部会根据 URL 参数在运行时选择具体扩展实现
- 使用例子:
1
2
3
4
5
6public interface Protocol {
Exporter export(Invoker invoker);
}- 当上面调用
export()方法时,Dubbo 会根据 URL 的protocol或defaultProtocol参数决定使用哪个协议实现
- 当上面调用
- 表示由 Dubbo 自动生成
@Adaptive标注在扩展类上(较少使用)- 表示该扩展类是一个手工编写的自适应扩展类
- Dubbo 将直接使用这个扩展类,而不会再自动生成代理类
- 一般用于非常复杂的场景,比如需要手写逻辑替代 Dubbo 自动生成代理类
- 目前 Dubbo 中仅有两个扩展类标注了
@Adaptive,分别是AdaptiveCompiler和AdaptiveExtensionFactory,表示这些扩展类的加载逻辑由人工编码(静态编码)完成 - 使用例子:
1
2
3
4
5
6
7
8
9
public class CustomAdaptiveCompiler implements Compiler {
public Class<?> compile(String code, ClassLoader loader) {
// 自定义代码编译逻辑
}
}
- 使用注意事项
- 接口上不能标注
@Adaptive - 接口方法上可以标注
@Adaptive,表示使用 Dubbo 自动生成的代理类,适用大多数场景 - 扩展类上可以标注
@Adaptive,表示使用手写的自适应逻辑,适用特殊复杂场景
- 接口上不能标注
- 主要作用
@Activate注解(自动激活)- 主要作用
- 表示当扩展类被自动加载时,满足
group、value、order等条件,该扩展会自动加入扩展链,无需手动在配置中指定扩展名。
- 表示当扩展类被自动加载时,满足
- 常见使用方式
@Activate标注在扩展类上(最常见)- 扩展类在满足 Activate 条件时自动激活
- 一般用于 Filter、Router、ExporterListener、Registry 等扩展点
- 使用例子:
1
2
3
4
5
6
7
8
9
10
public class MyProviderFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation) {
// 自定义过滤逻辑
return invoker.invoke(invocation);
}
}
@Activate标注在接口方法上(较少使用)- 表示该接口方法返回的扩展类实例在调用时可自动激活
- 可结合 URL 参数或
group、value、order条件进行动态控制 - 使用例子:
1
2
3
4
5
6public interface SomeFactory {
Extension create();
}
- 典型使用
@Activate自动激活的扩展点(最常见场景)- Filter 自动生效(过滤器)
- Router 自动生效(路由)
- ExporterListener 自动生效(监听服务暴露)
- Registry(如注册中心相关监听、通知)自动生效
- 可使用
@Activate激活,但通常是通过 URL 指定或者明确选择的的扩展点- Cluster(有些装饰逻辑可能自动封装,但多数是由 Dubbo 主流程显式选择)
- Protocol(支持
@Activate,但较少用于自动激活,一般是 URL 或显式选择) - ProxyFactory(可自动激活,但通常通过指定实现选择)
- 使用注意事项
- 接口上不能标注
@Activate
- 接口上不能标注
- 主要作用
总结
@Adaptive(自适应)的作用:让扩展在运行时根据 URL 或条件动态选择具体扩展实现;标注在方法上,则表示由 Dubbo 自动生成代理类;标注在类上,则表示使用手写的自适应逻辑。@Activate(自动激活)的作用:让扩展类在满足 Activate 条件时自动激活加入调用链;可以省略,但省略后不会自动激活,必须通过扩展名主动加载,例如:extensionLoader.getExtension("customizedProtocol");。
SPI 机制的实现原理
- (1) 使用
@SPI注解- 在接口上标注
@SPI注解,表示这个接口是 Dubbo 的扩展点 @SPI注解可以设置参数:- 比如
@SPI("defaultName"):指定该扩展点的默认扩展名- 当调用
getDefaultExtension()或未明确指定扩展名时,会使用此默认实现
- 当调用
- 如果
@SPI注解不设置参数,则该扩展点没有默认实现,必须在调用时明确指定扩展名。
- 比如
- 在接口上标注
- (2) 创建扩展配置文件
- 扩展配置文件存放位置:
META-INF/dubbo/接口全限定名文件 - 文件名称是:接口全限定名
- 文件内容是:
扩展名 = 扩展类全限定名
- 扩展配置文件存放位置:
- (3) 由
ExtensionLoader类加载扩展- 读取扩展配置文件
- 建立 “扩展名 → 扩展类” 映射
- 完成实例化、缓存、生命周期管理
- 首次使用时才加载(即懒加载)
- (4) 支持通过
@Adaptive注解动态选择扩展- Dubbo 会自动生成代理类,根据 URL 参数在运行时选择具体扩展实现
- (5) 支持通过
@Activate注解自动激活扩展- 根据
group、value、order等条件自动激活扩展,可实现过滤器链(Filter)等机制
- 根据
SPI 机制的加载流程
- Dubbo SPI 机制加载扩展的核心步骤:
- (1) 读取并解析配置文件
- (2) 缓存所有扩展实现类
- (3) 基于用户执行的扩展名,实例化对应的扩展实现类
- (4) 执行扩展实例属性的 IOC 注入(基于
Setter注入),以及实例化扩展的包装类,实现 AOP 特性
- Dubbo SPI 机制加载扩展的整个流程:
![]()
SPI 机制的源码剖析
Dubbo SPI 机制的核心实现源码
- 核心实现源码位于
com.alibaba.dubbo.common.extension.ExtensionLoader类- 负责加载扩展配置文件、创建扩展实例、缓存实例、处理依赖注入,以及生成 Adaptive 代理对象或包装 Wrapper 扩展等
ExtensionLoader类的核心方法有以下几个(不限于):getExtensionLoader()—— 获取指定接口类型的ExtensionLoader实例,是 SPI 的入口。getExtensionClass()—— 获取指定扩展名对应的扩展实现类。loadExtensionClasses()—— 加载接口对应的所有扩展实现类,解析META-INF/dubbo配置文件。getExtension()—— 根据扩展名获取单个扩展实例(普通扩展)。getActivateExtension()—— 获取符合条件的自动激活扩展集合。createExtension()—— 创建普通扩展实例,并注入其依赖扩展。createAdaptiveExtension()—— 创建 Adaptive 代理对象,实现方法级别自适应扩展逻辑。createAdaptiveExtensionClassCode()—— 生成 Adaptive(自适应)动态代理类的 Java 源码,用于根据 URL 参数选择具体扩展实现。
![]()
![]()
- 核心实现源码位于
Dubbo SPI 机制的源码剖析
(1) 通过反射创建扩展类实例
- Dubbo 使用
Class.newInstance()或构造函数反射来创建扩展类对象实例。 - 扩展类实例化的逻辑集中在
ExtensionLoader.createExtension()中。
- Dubbo 使用
(2) 依赖注入:
injectExtension()方法- 扩展类实例创建后,Dubbo 会遍历其所有
setter方法。 - 如果某个
setter方法的参数类型是另一个扩展点接口:- 则通过
ExtensionLoader.getExtension()获取对应扩展实例 - 调用
setter方法将其注入
- 则通过
- 这相当于一个轻量级的 IOC 容器,但只对扩展点生效。
- 扩展类实例创建后,Dubbo 会遍历其所有
(3) 包装类(Wrapper)的处理机制
- Dubbo 支持 “包装类” 扩展机制,用于为扩展点添加通用逻辑(如 Filter 链、Cluster 封装)。
- wrapper 类特征:
- 它有一个构造函数参数刚好为当前扩展接口,例如:
1
public XxxWrapper(Xxx extension) {}
- Dubbo 以此判断该类是 Wrapper,而不是普通扩展实现。
- 它有一个构造函数参数刚好为当前扩展接口,例如:
(4) Wrapper 的装饰顺序和依赖注入
- Dubbo 会先创建原始扩展实例(
realExtension)。 - 然后按定义顺序用多个 Wrapper 层层包装实例:
1
Wrapper3(Wrapper2(Wrapper1(realExtension)))
- 每个 Wrapper 创建后也会走
injectExtension(),同样支持注入依赖扩展点。
- Dubbo 会先创建原始扩展实例(
(5) Wrapper 的核心作用
- 封装通用逻辑,例如:
- Filter 责任链
- Cluster 对 Invoker 的封装
- 监控、日志、限流等逻辑
- 使用装饰器模式,不修改原始扩展类即可增强行为。
- 封装通用逻辑,例如:
(6) 最终返回的扩展实例
- 扩展实例 = 多层 Wrapper + 真实扩展类
- 所有依赖扩展均自动注入完成
- 整个对象结构由
ExtensionLoader全面管理
Dubbo SPI 机制源码剖析的总结
在 Dubbo SIP 机制中,通过 Class 反射类来构造 Class 对象实例,injectExtension() 方法通过 setter 注入扩展类中依赖的其他扩展点。包装类 Wrapper 封装了通用的逻辑,通过有无当前扩展接口参数的构造函数来判断,并注入依赖扩展。
Dubbo 中的 Invoker
Invoker 的概念
- Invoker 是 Dubbo 的核心模型,也是实体域,其它模型都围绕它展开或可以转换为它。
- Invoker 表示一个可执行体,可以对其发起
invoke()调用,它可能是本地实现、远程实现,也可能是集群实现。在 Dubbo 中,所有需要代理执行的方法都可以通过 Invoker 进行抽象和转换。 - Invoker 是由 ProxyFactory 创建的,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory,即默认使用 Javassist 字节码操作技术来实现动态代理。下图展示了
JavassistProxyFactory.getInvoker()方法的核心源码:

Wrapper 的概念
- Wrapper 的核心概念
- 主要作用:
- 对目标类的方法和属性进行统一封装,提高方法调用和属性访问的效率。
- 实现原理:
- 通过动态生成字节码(默认基于 Javassist),避免依赖反射调用,从而提升方法调用性能
- 核心功能:
- 快速调用目标类的方法(通过
invokeMethod()调用) - 快速访问目标类的字段
- 缓存生成的 Wrapper 实例,减少重复生成字节码的开销
- 快速调用目标类的方法(通过
- 应用场景:
- 在 Dubbo 中,用于对服务接口或实现类进行统一代理和方法调用
- 提高 RPC 调用和本地方法调用的效率
- 主要作用:
- Wrapper 的核心方法
invokeMethod(Object instance, String methodName, Class<?>[] parameterTypes, Object[] args)- 作用:通过 Wrapper 对目标对象的方法进行调用分发。
- 说明:Wrapper 会根据传入的对象(
instance)、方法名(methodName)、参数类型和参数值,将调用分发到具体的方法实现上,从而实现对目标类方法的统一调用封装,避免直接使用反射的性能开销。
getWrapper(Class<?> c)- 作用:获取指定类对应的 Wrapper 实例(通常是已缓存的 Wrapper)。
- 说明:调用
getWrapper()时,若 Wrapper 已经生成,会直接返回缓存的实例;否则,会调用makeWrapper()动态生成一个新的 Wrapper。通过缓存机制,可以减少重复生成 Wrapper,提高性能。
makeWrapper(Class<?> c)- 作用:为指定的类生成一个 Wrapper 类实例。
- 说明:Wrapper 内部通过动态生成字节码(默认使用 Javassist)封装目标类的方法和属性,从而提供快速的方法调用和属性访问能力。生成后的 Wrapper 可以高效地对目标类进行方法调用和字段操作,而不需要每次都通过 Java 反射来实现。

- Invoker 与 Wrapper 的关系
- Invoker 用来执行方法调用,而 Wrapper 用来辅助 Invoker 更高效地调用目标对象的方法。
- Wrapper:通过字节码技术(默认使用 Javassist)为目标类动态生成高性能的 “方法调用辅助类”,避免使用反射来调用方法。
- Invoker:Dubbo 的核心执行体,通过
invoke()调用目标对象的方法。
- 简而言之,Invoker 在执行方法调用时,会使用 Wrapper 动态生成的快速调用代码来调目标对象的方法。
- Invoker 用来执行方法调用,而 Wrapper 用来辅助 Invoker 更高效地调用目标对象的方法。







