Dubbo 2 巩固教程之二
大纲
- Dubbo 2 巩固教程之一、Dubbo 2 巩固教程之二、Dubbo 2 巩固教程之三
- Dubbo 2 巩固教程之四、Dubbo 2 巩固教程之五、Dubbo 2 巩固教程之六
- Dubbo 2 巩固教程之七
前言
学习资源
Dubbo 深入理解
学习建议
学习内容
- 接口代理、注册中心、配置加载
学习方法
接口代理:Dubbo 的接口代理是如何实现的?注册中心:Dubbo 在 Zookeeper 上面干了什么?配置加载:Dubbo 配置加载流程、不同粒度配置的覆盖关系是怎样?
特别注意
在学习 Dubbo 的底层源码时,建议使用原生 API 的方式来使用 Dubbo,这样更容易探寻 Dubbo 内部细节的入口。
接口代理
接口代理的底层实现
- Dubbo 接口代理的底层源码
- Dubbo 基于接口的代理实现
ReferenceConfig.get()->ReferenceConfig.init()->ReferenceConfig.createProxy()->StubProxyFactoryWrapper.getProxy()->JavassistProxyFactory.getProxy()->DubboInvoker.doInvoke()
- Nettty 在远程过程调用的应用
NettyClient.doOpen()->NettyClientHandlerNettyServer.doOpen()->NettyHandler
- Dubbo 基于接口的代理实现
支持的动态代理策略
Dubbo 支持的动态代理策略有以下三种
- Javassist(基于字节码):默认,使用 Javassist 字节码操作库生成代理类。
- JDK 动态代理(基于反射):使用 JDK 动态代理生成代理类。
- CGLib(基于 ASM):使用 CGLib 字节码操作库生成代理类。
Dubbo 默认使用 Javassist 来实现动态代理,而不是使用 CGlib 的原因如下
| 原因 | 说明 |
|---|---|
| 功能冗余 | Dubbo 只需要接口代理,CGlib 面向类代理,其优势无法发挥。 |
| Javassist 足够优秀 | 生成速度快,运行性能高,API 友好,完全可以替代 CGlib。 |
| 依赖冲突 | Spring 等框架也使用 CGlib,如果 Dubbo 也使用 CGlib 容易产生版本冲突和类加载问题。 |
| 维护成本高 | ASM 对 JDK 版本敏感,Dubbo 不希望承担额外的版本升级压力,增加维护成本。 |
| 社区策略 | Dubbo 官方逐步废弃 CGlib,未来版本彻底移除。 |
提示
Dubbo 默认使用 Javassist 字节码操作来生成代理类,还可以通过 Dubbo 的 SPI 扩展机制配置用户自己的动态代理实现。
注册中心
Dubbo 官方文档
更多关于 Dubbo 注册中心的介绍请看 Dubbo 官方文档 - 注册中心与服务发现。
支持的注册中心
Dubbo 支持的注册中心有以下几种(不限于):

Dubbo 3 支持的注册中心类型
在 Dubbo 3 中,默认支持 ZooKeeper 与 Nacos 作为注册中心,而且 Dubbo 扩展生态提供了 Consul、Etcd、Redis、Multicast 等注册中心的扩展实现,还支持 Kubernetes、Mesh 体系的服务发现。
ZK 注册中心使用
ZooKeeper 注册中心的底层源码
- Dubbo 中 ZooKeeper 注册中心相关的底层源码:
- 将服务注册到 ZooKeeper
ZookeeperRegistry.doRegister()
- 从 ZooKeeper 订阅服务
ZookeeperRegistry.doSubscribe()
- 将服务注册到 ZooKeeper
ZooKeeper 注册中心的存储结构
- Dubbo 在 ZooKeeper 注册中心中的数据存储结构和数据格式(如下图所示):

1 | /dubbo |
- Dubbo 在 ZooKeeper 注册中心的核心目录说明:
- 服务级注册 / 配置目录
- 目录路径:
/dubbo/{serviceInterface}/{type}/{serviceInterface}是具体服务接口的全限定名{type}包括:providers:服务提供者注册的 URLconsumers:服务消费者注册的 URLrouters:服务级的路由规则configurators:服务级的动态配置规则
- 主要作用:
- 用于存储服务提供者、服务消费者的注册信息
- 用于存储服务级的路由规则(如条件路由、标签路由等)
- 用于存储服务级的动态配置规则(如超时时间、重试次数、负载均衡策略等)
- 适用场景:
- 服务提供者与服务消费者的注册发现
- 针对某个服务接口的专属路由规则(如条件路由、标签路由等)
- 针对某个服务接口的的专属治理策略(如超时时间、重试次数、负载均衡策略等)
- 服务接口级的灰度发布、金丝雀发布
- 服务接口级的熔断、降级、流控策略
- 服务接口级的黑白名单 / 权限控制
- 服务接口级的 Mock(服务降级) / 故障模拟配置
- 针对特定
version/group的差异化治理
- 目录路径:
- 服务级注册 / 配置目录
查看 ZooKeeper 注册中心的数据
- 查看 ZooKeeper 的节点信息(Root Node)
1 | [zk: localhost:2181(CONNECTED) 7] ls /dubbo |
- 查看 ZooKeeper 的节点信息(Service)
1 | [zk: localhost:2181(CONNECTED) 9] ls /dubbo/com.clay.dubbo.service.DemoService |
- 查看 ZooKeeper 的节点信息(Provider - 服务提供者),默认通过
URLEncoder进行编码
1 | [zk: localhost:2181(CONNECTED) 10] ls /dubbo/com.clay.dubbo.service.DemoService/providers |
- 将上述的节点信息(Provider - 服务提供者)通过
URLDecoder解码后,得到的内容如下:
1 | [dubbo://127.0.0.1:20880/com.clay.dubbo.service.DemoService?anyhost=true&application=dubbo-provider-application&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.clay.dubbo.service.DemoService&methods=sayHello&pid=24895&release=2.7.6&side=provider×tamp=1735287128612] |
多注册中心注册
Dubbo 支持同一个服务向多个注册中心同时注册,或者不同的服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。
- 同一个服务注册到不同的注册中心。比如:中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将青岛的服务同时注册到两个注册中心,具体配置如下图所示:

- 不同的服务注册到不同的注册中心。比如:CRM 系统中有些服务是专门为国际站设计的,有些服务是专门为中文站设计的,具体配置如下图所示:

- 单独引用不同注册中心的服务。比如:CRM 系统中需要同时调用中文站和国际站的 PC2 服务,而 PC2 服务在中文站和国际站均有部署,接口及版本号都一样,但连接的数据库不一样,具体配置如下图所示:

- 同时引用不同注册中心的同名服务,可以使用竖号分隔多个不同注册中心地址。比如:CRM 系统中需要同时调用中文站和国际站的 Pay 服务,而 Pay 服务在中文站和国际站均有部署,接口及版本号都一样,连接的数据库也一样,具体配置如下图所示:

一个服务多个注册中心的应用场景

注册中心的扩展
扩展接口
- 扩展说明
- 负责服务注册与发现
- 扩展接口
org.apache.dubbo.registry.RegistryFactoryorg.apache.dubbo.registry.Registry
- 扩展配置
1
2
3
4
5
6<!-- 定义注册中心 -->
<dubbo:registry id="xxx1" address="xxx://ip:port"/>
<!-- 设置注册中心(服务提供者),如果没有配置 registry 属性,将在 ApplicationContext 中自动扫描 registry 配置 -->
<dubbo:service registry="xxx1"/>
<!-- 设置默认注册中心(服务提供者缺省配置),当 <dubbo:sevice> 没有配置 registry 属性时,默认会使用此配置 -->
<dubbo:provider registry="xxx1"/> - 扩展契约
![]()
![]()
SPI 扩展机制
Dubbo 支持通过 Registry SPI 机制扩展更多的注册中心实现到 Dubbo 生态,比如 Dubbo 官方通过该机制扩展支持了 Consul、Etcd 等注册中心。
SPI 机制详解
Java SPI 机制
- 概述
- Java 原生支持 SPI 机制,SPI 全称为 Service Provider Interface,是一种服务发现机制。
- SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器(
ServiceLoader)读取配置文件,加载具体的实现类。 - 这样就可以在运行时,动态为接口替换实现类。因此,开发者可以很容易的通过 SPI 机制为程序提供拓展功能(比如插件功能)。
- 比如:运行时通过
ServiceLoader等工具扫描依赖的 Jar 包,在其中查找对应的文件,并加载指定的实现类ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
- 使用步骤
- (1) 定义接口
- (2) 实现多个接口实现类
- (3) 在项目的
META-INF/services目录中新增全限定接口名文件,指定具体的实现类- 文件名是接口的全限定名
- 文件内容是接口实现类的全限定名
- 应用场景
- SPI 机制常用于插件式扩展。
- 比如:如果你在开发一个框架,可以通过 SPI 让外部开发者编写插件,扩展框架的功能,而不必修改框架的源码。
- 典型案例
- JDBC
- Java 标准库只定义了一套 JDBC 接口,并没有真正的实现。
- 数据库厂商(如 MySQL、Oracle)会提供自己的实现,并通过 SPI 机制声明在
resources/META-INF/services/目录中。 - 运行时,Java 会根据项目引入的数据库驱动 Jar 包,自动找到对应的 JDBC 实现类。
- JDBC
- 概述
Dubbo SPI 机制
- 概述
- Dubbo 并未使用 Java SPI 机制,而是重新实现了一套功能更强的 SPI 机制。
- Dubbo SPI 机制的相关逻辑被封装在
ExtensionLoader类中,通过ExtensionLoader,用户可以加载指定的实现类,具体使用步骤跟 Java SPI 稍有不同。
- 工作原理
- (1) 接口声明
- 在 Dubbo 中,如果某个接口需要支持 SPI 扩展,就会加上
@SPI注解,比如Protocol接口:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 接口定义
public interface Protocol {
int getDefaultPort();
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
void destroy();
}
// 加载实现类
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); @SPI("dubbo")表示默认实现是dubbo。@Adaptive表示该方法会生成代理逻辑,运行时根据参数动态选择实现类。
- 在 Dubbo 中,如果某个接口需要支持 SPI 扩展,就会加上
- (2) 实现类配置
- Dubbo 在自己 Jar 包中的
resources/META-INF/dubbo/internal/路径下提供了一个配置文件,文件名是接口的全限定名,比如:1
resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
- 配置文件的内容是
key=实现类的全限定名,key 对应@SPI注解中的扩展名称,比如:1
2
3dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
- Dubbo 在自己 Jar 包中的
- (3) 默认实现加载
- 如果用户没有配置扩展,Dubbo 会根据
@SPI("dubbo")的默认值dubbo,从配置文件中加载对应的实现类DubboProtocol。 - 这也是 Dubbo 默认使用 Dubbo 协议作为 RPC 通信协议的原因。
- 如果用户没有配置扩展,Dubbo 会根据
- (4) 动态切换实现类
- 在
Protocol接口中,有两个方法加了@Adaptive注解。 - Dubbo 会在运行时生成代理类,在代理方法内部根据传入的 URL 参数的
protocol值决定使用哪个实现类。 - 如果 URL 参数中没指定协议,就用默认的
dubbo;如果指定了其他值(如http),则加载对应的实现类。
- 在
- (1) 接口声明
- 概述
配置加载
Dubbo 官方文档
更多关于 Dubbo 配置加载的介绍请看 Dubbo 官方文档 - 配置加载流程,附文档截图。
Dubbo 的配置来源
- Dubbo 的四种配置来源和优先级高低如下所示,优先级高的会覆盖优先级低的
| 优先级 | 配置来源 | 说明 |
|---|---|---|
| 1 | JVM System Properties,通过启动参数 -D 指定 | 最高优先级,通常用于紧急覆盖配置,无需改代码或配置文件,应用重启即可生效。 |
| 2 | Externalized Configuration,外部化配置 | 如 Apollo、Nacos、Spring Cloud Config 等配置中心动态下发的配置。Dubbo 运行时会监听这些配置的变化,支持热更新。 |
| 3 | 编程接口配置,比如 ServiceConfig、ReferenceConfig 等 | 在代码中直接通过 ServiceConfig、ReferenceConfig 等 API 设置的参数。优先级低于外部化配置,便于后期通过配置中心进行覆盖。 |
| 4 | 本地配置文件,比如 dubbo.properties | 最低优先级,通常用于本地开发,或作为兜底默认值。 |

Dubbo 的核心配置类
Dubbo 的配置类分为 4 大类:应用配置、服务消费方配置、服务提供方配置、公共方法配置。
- 应用配置
ApplicationConfig:配置当前应用的信息,不管该应用是服务提供方还是服务消费方RegistryConfig:配置连接注册中心相关的信息MonitorConfig:配置连接监控中心相关的信息(可选)ModuleConfig:用于配置当前模块的信息(可选)
- 服务消费方配置
RefrenceConfig:创建一个远程服务代理,一个引用可以指向多个注册中心ConsumerConfig:当ReferenceConfig中某属性没有配置时,会采用此配置的缺省值(可选),即ConsumerConfig相当于服务消费方的全局配置
- 服务提供方配置
ProtocolConfig:配置提供服务的协议信息,协议由提供方指定,消费方被动接受ServiceConfig:暴露单个服务,定义服务的元信息,单个服务可以用多个协议暴露、注册到多个注册中心ProviderConfig:当ProtocolConfig和ServiceConfig中某属性没有配置时,会采用此配置的缺省值(可选),即ProviderConfig相当于服务提供方的全局配置
- 公共方法配置
MethodConfig:指定方法级别的配置信息,如超时时间、重试次数、负载均衡策略等,可配置在ReferenceConfig和ServiceConfig下ArgumentConfig:指定方法参数的配置信息,如参数验证、回调方法等。
不同配置类之间的关系
- 在 Dubbo 中,配置类之间的引用和继承关系如下图所示(不同版本有所区别)。在引用和继承关系图中,左边是服务提供方的相关配置,右边是服务消费方的相关配置,中间是双方的共享配置,下边是方法和方法参数的相关配置。

ReferenceConfig引用了ConsumerConfig,而ServiceConfig引用了ProviderConfig。- 如果没有进行
ReferenceConfig的配置,默认使用ConsumerConfig的配置。 - 如果没有进行
ServiceConfig的配置,默认使用ProviderConfig的配置。
不同粒度配置的覆盖关系
以超时时间(timeout)的配置为例,下图显示了 Dubbo 配置的查找顺序(从上到下,配置的优先级逐渐降低),其它配置如 retries、loadbalance 等类似。蓝色部分表示服务消费方的配置,绿色部分表示服务提供方的配置。

配置的优先级
- 优先级高低:方法级别的配置 > 接口级别的配置 > 全局的配置,优先级高的会覆盖优先级低的
精确优先:方法级别的配置优先,接口级别的配置次之,全局的配置再次之。消费方配置优先:如果级别一样,则服务消费方的配置优先,服务提供方的配置次之。
- 其中,服务提供方的配置信息,是通过 URL 经由注册中心(比如 ZooKeeper)传递给服务消费方。
- 优先级高低:方法级别的配置 > 接口级别的配置 > 全局的配置,优先级高的会覆盖优先级低的
配置的覆盖规则
- 值得注意的是,下面的
>符号可以理解为 “覆盖”。 - 方法级别的配置 > 接口级别的配置,即小 Scope(范围)配置优先。
- 服务消费方的配置 > 服务提供方的配置 > 全局配置。
- 最后是 Dubbo Hard Code(硬编码)的配置(详见官方配置文档)。
- 值得注意的是,下面的
在 Dubbo 框架中,推荐在服务提供方(Provider)尽量多配置服务消费方(Consumer)相关的属性,主要原因如下:
- (1) 服务提供方更了解服务性能特征:作为服务的实现者,Provider 比 Consumer 更清楚服务的性能参数,例如方法调用的合理超时时间、重试次数等关键配置。
- (2) Provider 配置可作为 Consumer 的缺省值:当 Provider 配置相关属性后,如果 Consumer 未显式配置对应项,则会自动使用 Provider 的配置值。这样既避免了 Consumer 不配置时直接使用其全局默认值(可能对当前服务不合理),也提升了配置的可控性和合理性。
- (3) 此外,从实践角度建议,应由服务提供方设定超时时间、重试次数等关键配置,因为服务执行所需的时长通常只有服务提供方最清楚。这样也能减轻 Consumer 的配置负担,特别是当一个 Consumer 调用多个服务时,无需逐一关注每个服务的超时设置。
- (4) 从配置约定上看,理论上在
ReferenceConfig中,除了interface为必需配置项之外,其余所有配置项均可缺省。此时框架会自动逐级查找缺省配置,优先使用ConsumerConfig、ServiceConfig或ProviderConfig中提供的默认值。
提示
- Dubbo 的引用默认是延迟初始化的,只有引用被注入到其它 Bean,或被
getBean()获取才会初始化。如果需要饥饿加载,即没有被引用也立即生成动态代理对象,可以配置:<dubbo:reference ... init="true"/>。 - 在服务消费方配置一个接口级别的配置超时时间是 5000,而在服务提供方配置一个方法级别精确的超时时间为 1000,配置为
<dubbo:method name="getUserList" timeout"1000">,这个情况下生效的是服务提供方配置的超时时间 1000;因为方法级别的配置优先级更高,只有配置级别一样时,服务消费方的配置才优先。
不同配置文件的覆盖关系
下图显示了 Dubbo 配置文件的优先级高低(从上到下,优先级逐渐降低),优先级高的会覆盖优先级低的。

- 配置文件的优先级
- JVM 启动参数(
-D)- 优先级最高
- 可在服务部署和启动时通过
-D参数动态覆盖配置,例如修改协议端口:1
java -Ddubbo.protocol.port=20890 -jar app.jar
- 适用于临时 / 紧急覆盖配置,无需改代码或配置文件,应用重启即可生效。
- XML 配置文件
- 优先级次于 JVM 启动参数(
-D) - 如果在 XML 文件中配置了某项内容,则 Properties 配置文件(如
dubbo.properties)中对应的配置项将失效。
- 优先级次于 JVM 启动参数(
- Properties 配置文件
- 优先级最低
- 仅当 XML 中未配置某项内容时才会生效,相当于默认值。
- 通常用于本地开发存放公共或共享配置,例如应用名称等基础配置。
- JVM 启动参数(
配置项分类和配置项格式
配置项的分类
- (1) 服务发现:表示该配置项用于服务的注册与发现,目的是让服务消费方找到服务提供方。
- (2) 服务治理:表示该配置项用于治理服务间的关系,或为开发测试提供便利条件。
- (3) 性能调优:表示该配置项用于调优性能,不同的配置项对性能会产生影响。
配置项的格式

Dubbo 配置参考手册
Dubbo 完整的可配置项列表可以看 Dubbo 官方文档(最新版本)- 配置项参考手册,附文档截图。
Dubbo 支持的 XML 配置标签
Dubbo 支持的 XML 配置标签如下表所示:

Dubbo 的外部化配置(配置中心)
Dubbo 外部化配置的介绍
Dubbo 外部化配置和其他本地配置在内容和格式上并没有任何区别,可以简单理解为
dubbo.properties的外部化存储,配置中心更适合将一些公共配置(如注册中心、元数据中心等配置)抽取出来,以便做集中管理。Dubbo 外部化配置的主要目的是实现配置的集中式管理,支持的成熟配置系统有:ZooKeeper、Apollo、Nacos、Etcd、Consul,而 Dubbo 做的主要是支持这些配置系统正常工作,让集成更加方便。
举个例子:当 Dubbo 使用 Apollo、Nacos 等配置中心后,可以将以下全局配置(比如注册中心地址、元数据中心地址、协议、QOS 等)统一存储在配置中心,这样应用在本地就不再需要配置。
1
2
3
4
5
6
7
8
9
10
11
12# 将注册中心地址、元数据中心地址、协议、QOS 等配置存储在配置中心(如 Nacos),这样可以做到统一管理、减少开发侧感知
=true
=33333
=zookeeper://127.0.0.1:2181
=true
=zookeeper://127.0.0.1:2181
=dubbo
=20880当 Dubbo 使用 ZooKeeper 作为配置中心时,ZooKeeper 中的数据存储结构如下图所示:
![]()
当 Dubbo 使用 ZooKeeper 作为配置中心时,Dubbo 在 ZooKeeper 中的目录结构分为两类:
- 全局级配置目录
- 目录路径:
/dubbo/config/dubbo/ - 主要作用:
- 存放全局生效的治理规则
- 例如:全局动态配置(Dynamic Config)、全局路由规则等
- 影响范围:
- 所有应用、所有服务均可使用
- 适用场景:
- 全局级的动态配置(默认超时时间、默认重试次数、默认负载均衡等)
- 全局级的路由规则(统一的流控、统一标签路由等)
- 全局级的黑白名单、限流、熔断、降级等治理策略
- 全局级的统一开关(如全局灰度、全局 Mock 开关)
- 全局运维规则下发(例如,统一切换流量策略)
- 目录路径:
- 应用级配置目录
- 目录路径:
/dubbo/config/{application}/{application}是应用名称
- 主要作用:
- 用于存放某个应用的动态配置或路由规则
- 影响范围:
- 同一个应用内的所有服务接口(不跨应用)
- 适用场景:
- 应用级的路由规则
- 应用级的动态配置(如自定义超时时间、重试次数)
- 条件路由、标签路由等应用级治理策略
- 应用级的黑白名单、限流、降级策略
- 为应用内所有服务统一设置默认负载均衡、超时等治理规则
- 针对特定应用的测试环境 / 灰度环境治理策略统一下发
- 目录路径:
- 全局级配置目录
Dubbo 外部化配置的优先级
- 优先级:外部化配置默认比本地配置拥有更高的优先级,因此这里配置的内容会覆盖本地配置值。
- 作用域:外部化配置有全局和应用两个级别,全局配置是所有应用共享的,应用级配置是由每个应用自己维护且只对自身可见的。当前已支持的扩展实现有 Zookeeper、Apollo、Nacos 等。
Dubbo 外部化配置的使用步骤
(1) 增加
config-center配置(用于指定配置中心地址),比如:<dubbo:config-center address="nacos://127.0.0.1:8848"/>(2) 在相应的配置中心(Zookeeper、Nacos 等)增加全局配置项(如下图所示,以 Nacos 为例)
- 开启外部化配置后,
registry、metadata-report、protocol、qos等全局范围的配置理论上都不再需要在应用中配置,应用开发侧专注业务服务配置,一些全局共享的全局配置转而由运维人员统一配置在远端配置中心。 - 这样能做到的效果就是,应用只需要关心:
- 服务暴露、订阅服务;
- 配置中心地址当部署到不同的环境时,其他配置就能自动的被从对应的配置中心读取到。
- 举例来说,每个应用中 Dubbo 相关的全局配置(比如
application.yml)只要有以下内容可能就足够了,其余的配置都托管给相应环境下的配置中心:1
2
3
4
5dubbo
application
name: demo
config-center
address: nacos://127.0.0.1:8848
![]()
- 开启外部化配置后,




