Dubbo2 巩固教程之五
大纲
- Dubbo 2 巩固教程之一、Dubbo 2 巩固教程之二、Dubbo 2 巩固教程之三
- Dubbo 2 巩固教程之四、Dubbo 2 巩固教程之五、Dubbo 2 巩固教程之六
- Dubbo 2 巩固教程之七
前言
学习资源
Dubbo 深入理解
Dubbo 设计原则
Dubbo 的整体架构
Dubbo 中存在四大组件:
- Provider: 服务提供者。
- Consumer: 服务消费者。
- Registry: 服务注册与发现的中心,提供目录服务,也称为服务注册中心。
- Monitor: 统计服务的调用次数、调用时间等信息的监控服务,并可以对服务设置权限、降级处理等,称为服务管控中心。

Dubbo 的功能性需求

Dubbo 的非功能性需求
Dubbo 发展之初遇到的问题
- 随着 Dubbo 在阿里内部使用越来越火热,堆积的需求也越来越多,Dubbo 项目组人手不足。
- Dubbo 项目外的人员希望也能加入进来丰富 Dubbo 生态,进行功能扩展开放,开闭原则(OCP)应声而出。
Dubbo 的非功能性需求:开放扩展
- Dubbo 需要留一些扩展点,让项目参与者尽量黑盒扩展,而不是白盒的修改代码,否则分支、质量、合并、冲突都会很难管理。
Dubbo 的微核心插件式设计
- Dubbo 是如何做到优雅的开放扩展
- 微核心 + 插件式,平等对待第三方
- 由一个插件生命周期管理容器构成微核心,核心不包括任何功能,这样可以确保所有功能都能被替换。
- 框架作者能做到的功能,扩展者也一定要能做到,以保证平等对待第三方。
- 因此,框架自身的功能也要用插件的方式实现,不能有任何硬编码。
- 首先需要做的是统一扩展点的加载方式
- 然后考虑采用什么样的扩展点加载方式
- 采用微核心 + 插件式架构。通常微核心会采用 Factory、IoC、OSGi 等方式管理插件生命周期。
- Dubbo 最初曾考虑使用 Spring IoC 或自己实现一个 IoC 容器,但最终选择了采用 Factory 方式管理插件,并曾经使用 JDK 的 SPI 机制来实现 Factory。
- 因此,Dubbo 中曾有一个已被废弃的
@Extension注解,最终被自己实现的 SPI 机制取代,也就是现在使用的@SPI注解。
- 微核心 + 插件式,平等对待第三方
Dubbo 的基本设计原则
- 采用 Microkernel + Plugin(微核心 + 插件)模式,Microkernel 只负责组装 Plugin,而 Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。
- 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
Dubbo 的组合式扩展设计
Dubbo 采用了组合式扩展:核心最小化(Protocol + Invoker + Exporter),其他能力通过分层与组合的方式添加。
扩充式扩展与增量式扩展
在对一个原有的功能设计扩展方案时,常见有两类扩展方式:
扩充式扩展:
- 在原有实现上做增强,让新旧功能共用同一套代码路径。
- 新功能通过在原逻辑内 “兼容” 方式实现。
- 旧功能也会受到新逻辑影响,即使不需要,也会额外承担开销。
增量式扩展:
- 原功能保持简单、保持原有代码路径不变。
- 新功能以独立实现新增,不入侵旧逻辑。
- 使用者按需选择使用旧实现或新实现,互不干扰。
举例,扩展一个 OSGI 序列化功能,要求功能明确,把流转成对象,对象转成流。
- 扩充式扩展的做法:
- 在原有序列化逻辑内部增加对 OSGi 序列化的支持。
- 无论是否需要 OSGi,都要先把 Stream 转成
byte[],才能满足新逻辑的处理流程。 - 最终结果:
- 新功能很容易实现,因为复用了旧逻辑。
- 但旧场景本来不需要额外的
byte[]中间转换,却被迫付出了额外成本(性能损耗、内存占用增加等)。
- 存在问题:
- 旧功能为新功能背负了不必要的成本。
- 增量式扩展的做法:
- 保留非 OSGi 的序列化代码,不改动、不增加额外中间步骤。
- 单独新增一个 OSGi 序列化实现,内部按 OSGi 规范自行处理。
- 若使用 OSGi 场景,则直接注入 / 依赖此实现即可。
- 实现好处:
- 旧功能不变,成本不变,新功能独立,互不影响。
- 扩充式扩展的做法:
| 扩展方式 | 特点 | 代价 | 适用场景 |
|---|---|---|---|
| 扩充式扩展 | 在原逻辑内兼容新功能,新旧走同一代码路径 | 旧功能被连带影响,性能可能下降 | 新功能非常轻量、对旧逻辑影响极小 |
| 增量式扩展 | 新旧逻辑独立实现,互不影响 | 需要维护多套实现 | 新功能对旧功能影响较大、开销较大,或不想污染旧逻辑 |
泛化式扩展与组合式扩展
在设计一个可扩展系统时,常见有两类扩展思路:
泛化式扩展
- 定义:
- 扩展点不断抽象化、泛化,将不同功能的要求都往同一个概念里塞。
- 本质是取所有功能的并集:新功能加入时,需要扩充原有抽象,使其能容纳更多概念。
- 缺点:
- 容易让扩展点逐渐臃肿。
- 新功能侵入原有核心模型,使系统越来越复杂。
- 许多 “非核心功能” 会被迫纳入核心抽象。
- 定义:
组合式扩展
- 定义:
- 将功能按职责正交拆分。
- 本质是取所有功能的交集,扩展点保持最小交集(最小必要抽象)。
- 新功能总是基于旧功能之上实现,通过组合、包装、链式调用等方式叠加,而不是修改核心抽象。
- 优点:
- 核心模型保持简洁稳定。
- 扩展功能彼此独立、可插拔。
- 系统不会因为扩展点膨胀而复杂化。
- 定义:
| 扩展方式 | 特点 | 问题 / 优点 |
|---|---|---|
| 泛化式扩展 | 抽象不断膨胀,取所有功能并集 | 模型臃肿,核心功能被污染 |
| 组合式扩展 | 抽象保持极简,通过包装 / 组合扩展 | 稳定、高内聚、可插拔、清晰分层 |
- 泛化式扩展存在的问题(以 Dubbo 为例)
- 在 Dubbo 中,如果选择泛化式扩展的做法,就会尝试把各种周边能力都塞进一个 “核心模型” 里,例如:
- 路由器(Router)
- 集群容错(Cluster)
- 负载均衡(LoadBalance)
- 服务发布者(Exporter)
- 服务订阅者(Directory)
- 一旦把它们都视为 “核心功能”,结果就是:
- 核心抽象过度膨胀
- 层次不清晰
- 新功能必须入侵原核心框架
- Dubbo 的做法是采用组合式扩展:核心极简 + 分层组合
- Dubbo 的真正核心抽象并不包括上述能力
- Dubbo 的核心包括三部分:
- Invoker
- 代表一个可调用体
- 是 Dubbo 的最小执行单元
- 类似 “函数指针”,统一抽象了调用逻辑
- Protocol
- 负责远程通信的协议栈
- 决定如何导出服务、如何引用服务
- Exporter
Protocol.export()的结果- 表示一个已经发布出去的、可被消费的服务
- Invoker
- 由此 Dubbo 形成两个主要层次:
- 协议层(Protocol Layer)
- 负责通信协议、序列化、网络传输等
- 核心抽象非常稳定:
Protocol + Invoker + Exporter
- 路由层(Router Layer)
- 在协议层之上进行组合式增强
- 路由、负载均衡、容错等都属于这一层
- 非核心能力都通过组合 Invoker 的方式增强,例如:
- Router → 过滤或选择部分 Invoker
- LoadBalance → 根据负载均衡策略选择一个 Invoker
- Cluster → 封装多 Invoker 提供集群能力
- Filter → 链式增强一个 Invoker
- 因此,这些(路由、负载均衡、容错等)都不是 Dubbo 的核心,而是路由层的扩展能力
- 协议层(Protocol Layer)
- 在 Dubbo 中,如果选择泛化式扩展的做法,就会尝试把各种周边能力都塞进一个 “核心模型” 里,例如:
总结
Dubbo 的设计哲学是典型的组合式扩展:核心最小化(Protocol + Invoker + Exporter),其他能力通过分层与组合的方式添加。
Dubbo 的最大化复用设计
- 每个扩展点只封装一个变化因子
- 比如:想扩展 RPC 协议,可能只是想更换一种通信传输方式(比如,将 Netty 替换为 Mina),其他的都复用。需要将协议拆解如下:

Dubbo 的全管道式设计
Dubbo 采用了截面拦截(AOP 风格),而非模板方法(Template Method)设计模式。
模板方法设计模式的局限
- 模板方法通常用于代码复用:
- 将公共逻辑抽取到父类的模板方法中;
- 将可变逻辑抽象为钩子方法,由子类实现。
- 但在 Framework 场景中,当 “公共逻辑” 本身非常复杂、包含大量功能点、并且新能力持续增加时:
- 父类会变得臃肿;
- 模板方法的结构会被扩展需求不断侵入;
- 覆盖点过多、难以维护;
- 扩展性差,一旦修改会影响所有子类。
- 因此,模板方法不适合承担 Dubbo 这种需要大量、可插拔扩展点的系统。
- 模板方法通常用于代码复用:
Dubbo 采用截面拦截
- Dubbo 将公共逻辑抽取出来,通过
Wrapper + Filter的方式进行组合扩展:- 每个扩展点实现一个独立的 Filter;
- Filter 在 Invoker 外层形成调用链(Pipeline);
- 责任链模式保证扩展能力可插拔、可按需组合;
- Wrapper(如
Protocol$Adaptive)负责将扩展链编织到 Invoker 调用中。
- 这种架构类似 AOP:扩展功能作为 “切面” 附加在 Invoker 调用前后,而不需要修改核心逻辑。
- Dubbo 将公共逻辑抽取出来,通过
Dubbo 中哪些功能是通过截面拦截实现的?
- 在 Dubbo 中,许多功能都属于对 Invoker 调用的增强,而非核心逻辑,可以通过截面拦截实现:
local本地调用mock服务降级generic泛化调用echo回声测试token令牌校验accesslog访问日志monitor调用统计count统计计数limit限流
- 上面这些都不是由 Invoker 继承自父类获得的功能,而是通过独立的 Filter(例如限流的
LimitFilter),以调用链方式进行扩展。 - 为什么 Dubbo 不把这些功能放在 Invoker 的父类中?原因很简单:
- 这会让父类极度臃肿,维护困难;
- 每加入一个新功能就要改父类,扩展困难;
- 父类逻辑会被功能点挤满,失去核心职责;
- 不同功能之间耦合严重,测试成本高;
- 无法做到 “按需添加” 扩展。
- 在 Dubbo 中,许多功能都属于对 Invoker 调用的增强,而非核心逻辑,可以通过截面拦截实现:
Dubbo 的外部最少概念
- 一致性概念模型:
- 保持尽可能少的概念,有助于理解,对于开放的系统尤其重要。
- 另外,各接口都使用一致的概念模型,能相互指引,并减少模型转换。

Dubbo 的十层整体设计

Dubbo 整体架构设计图(如上所示)的说明:
- 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
- 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
- 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
- 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。
Dubbo 的整体架构可以分为 10 层,从上到下依次是:
- (1) Service 层:接口层,给服务提供者和服务消费者来实现的(留给开发人员来实现)。
- (2) Config 配置层:对外配置接口,以
ServiceConfig、ReferenceConfig为中心,可以直接初始化配置类,也可以通过 Spring 解析配置生成配置类。 - (3) Proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以
ServiceProxy为中心,扩展接口为ProxyFactory。 - (4) Registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为
RegistryFactory、Registry、RegistryService。 - (5) Cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以
Invoker为中心,扩展接口为Cluster、Directory、Router、LoadBalance。 - (6) Monitor 监控层:RPC 调用次数和调用时间监控,以
Statistics为中心,扩展接口为MonitorFactory、Monitor、MonitorService。 - (7) Protocol 远程调用层:封装 RPC 调用,以
Invocation和Result为中心,扩展接口为Protocol、Invoker、Exporter。 - (8) Exchange 信息交换层:封装请求响应模式,同步转异步,以
Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient、ExchangeServer。 - (9) Transport 网络传输层:抽象 Mina 和 Netty 为统一接口,以
Message为中心,扩展接口为Channel、Transporter、Client、Server、Codec。 - (10) Serialize 数据序列化层:可复用的一些工具,扩展接口为
Serialization、ObjectInput、ObjectOutput、ThreadPool。
在 Dubbo 的十层整体设计中,各层之间的关系如下:
- (1) 在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。
- (2) 上图中的 Consumer 和 Provider 是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用 Client 和 Server 的原因是:Dubbo 在很多场景下都使用 Provider、Consumer、Registry、Monitor 来划分逻辑拓普节点,保持统一概念。
- (3) Cluster 是外围概念,所以 Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层的 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。
- (4) Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才会使用 Proxy 将 Invoker 转成接口,或者将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
- (5) Remoting 实现是 Dubbo 协议的实现,如果选择 RMI 协议,整个 Remoting 都不会用上的。Remoting 的内部划分为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina、Netty、Grizzly 的抽象,它也可以扩展为 UDP 传输,而 Exchange 层是在传输层之上封装了 Request 和 Response 语义。
- (6) Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。
Dubbo 官方文档
