- 2020
- 4.1 阿里系 RPC 框架
- 4.2 Dubbo 的两大设计原则
- 4.3 Dubbo 的三大领域模型
- 4.4 Dubbo 的四大组件
- 4.5 Dubbo 的十层架构
- (1) config 配置层
- (2) proxy 服务代理层
- (3) registry 注册中心层
- (4) cluster 路由层
- (5) monitor 监控层
- (6) protocol 远程调用层
- (1) exchange 信息交换层
- (2) transport 网络传输层
- (3) serialize 数据序列化层
- 4.6 Dubbo 框架架构
- 5.1 JDK 的服务发现机制 SPI
- 5.2 Dubbo 的服务发现机制 SPI
- 5.3 自适应机制 Adaptive
- (1) @Adaptive 修饰类
- (2) @Adaptive 修饰方法
- (2) 定义 adaptive 扩展类
- (1) 动态生成 Adaptive 类格式
- 5.4 包装机制 Wrapper
- 5.5 激活机制 Activate
- (2) 修改扩展类 AlipayOrder
- (3)修改扩展类 WeChatOrder
- (4) 定义扩展类 CardOrder
- (5)定义扩展类 CashOrder
- (6) 定义扩展类 CouponOrder
- 5.6 总结
- 5.7 Dubbo 的 SPI 源码解析
- (1) factory 实例的 loader 的 extensionFactory 实例是 null
- (2) 获取 factory 的 extensionLoader
- (3) 创建 Container 的 loader 的 extensionFactory
- (4) 获取 Protocol 的 extensionLoader
- (3) 定义 JavassistCompiler
分布式 RPC 框架
Dubbo(2.7)
课程讲义
主讲:Reythor 雷
2020
分布式 RPC 框架 Dubbo
第4章 Dubbo 的系统架构解析
4.1 阿里系 RPC 框架
l Dubbo:阿里巴巴 B2B
l HSF:淘宝
l SOFA:蚂蚁金服
4.2 Dubbo 的两大设计原则
Dubbo 框架在设计时遵循了两大设计原则:
l Dubbo 使用“微内核+插件”的设计模式。内核只负责组装插件(扩展点),Dubbo 的功能都是由插件实现的。Dubbo 作为一个优秀的 RPC 框架,一个 Apache 的顶级项目,其最大的亮点之一就是其优秀的无限开放性设计架构—“微内核+插件”的架构设计思想,使得其几乎所有组件均可方便的进行扩展、增强、替换。
l 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
JSON protocol:dubbo, host:ip,port:8888,interface:com.abc.service.SomeService
URL:dubbo://ip:8888/com.abc.service.SomeService?
4.3 Dubbo 的三大领域模型
为了对 Dubbo 整体架构叙述的方便,Dubbo 抽象出了三大领域模型。
l Protocol 服务域:是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
l Invoker 实体域:是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。其就是提供者代理对象。
l Invocation 会话域:它持有调用过程中的变量,比如方法名,参数等。远程调用信息。
4.4 Dubbo 的四大组件
Dubbo 中存在四大组件:
l Provider:暴露服务方,亦称为服务提供者。
l Consumer:调用远程服务方,亦称为服务消费者。
l Registry:服务注册与发现的中心,提供目录服务,亦称为服务注册中心
l Monitor:统计服务的调用次数、调用时间等信息的日志服务,亦称为服务监控中心
4.5 Dubbo 的十层架构
Dubbo 的架构设计划分为了 10 层。图中左边淡蓝色背景为服务 Consumer 使用的接口,右边淡绿色背景为服务 Provider 使用的接口,位于中轴线的为双方都要用到的接口。对于这
10 层,根据其总体功能划分,可以划分为三大层:
4.5.1 Business 层
该层仅包含一个 service 服务层,该层与实际业务逻辑有关,根据服务消费方和服务提供方的业务设计,实现对应的接口。
4.5.2 RPC 层
该层主要负责整个分布式系统中各个主机间的通讯。该层包含了以下 6 层。
(1) config 配置层
以 ServiceConfig 和 ReferenceConfig 为中心,用于加载并解析 Spring 配置文件中的 Dubbo 标签。
(2) proxy 服务代理层
服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory。
Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以运行的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
(3) registry 注册中心层
封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为 RegistryFactory、Registry、
RegistryService,可能没有服务注册中心,此时服务提供方直接暴露服务。
(4) cluster 路由层
封装多个提供者的路由和负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster、Directory、Router 和 LoadBalance,将多个服务提供方组合为一个服务提供方,实现对服务消费通明。只需要与一个服务提供方进行交互。
Dubbo 官方指出,在 Dubbo 的整体架构中,Cluster 只是一个外围概念。Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样用户只需关注 Protocol 层 Invoker 即可,加上
Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要
Cluster 的。
(5) monitor 监控层
RPC 调用时间和次数监控,以 Statistics 为中心,扩展接口 MonitorFactory、Monitor 和
MonitorService。
(6) protocol 远程调用层
封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocol、Invoker 和 Exporter。 Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。 Invoker 是实体域,它是 Dubbo 的核心模型,其他模型都是向它靠拢,或转换成它,它代表一个可执行体,可向它发起 Invoker 调用,它有可能是一个本地实现,也有可能是一个远程实现,也有可能是一个集群实现。
在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。
4.5.3 Remotting 层
Remoting 实现是 Dubbo 协议的实现,如果我们选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。具体包含以下三层:
(1) exchange 信息交换层
封装请求响应模式,同步转异步,以 Request 和 Response 为中心,扩展接口为 Exchanger 和 ExchangeChannel,ExchangeClient 和 ExchangeServer。
(2) transport 网络传输层
抽象和 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、
Client、Server 和 Codec。
(3) serialize 数据序列化层
可复用的一些工具,扩展接口为 Serialization、ObjectInput、ObejctOutput 和 ThreadPool。注:这里解析的源码为 dubbo-2.7.3 版本。
4.6 Dubbo 框架架构
4.6.1 将 Dubbo 源码工程导入 Idea
本例以 Dubbo2.7.3 为例进行源码解析。从官网下载到源码的 zip 包后解压后就可以直接
导入到 Idea 中。
4.6.2 Dubbo2.7 版本与 2.6 版本
Dubbo2.7 版本需要 Java 8 及以上版本。
2.7.0 版本在改造的过程中遵循了一个原则,即保持与低版本的兼容性,因此从功能层面来说它是与 2.6.x 及更低版本完全兼容的。2.7 与 2.6 版本相比,改动最大的就是包名,由原来的 com.alibaba.dubbo 改为了 org.apache.dubbo。
第5章 Dubbo 的内核解析
所谓 Dubbo 的内核是指,Dubbo 中所有功能都是基于它之上完成的,都是由它作为基础的。Dubbo 内核的工作原理由四部分构成:服务发现机制 SPI、自适应机制 Adaptive、包装机制 Wrapper 与激活机制 Activate。Dubbo 通过这四种机制实现了对插件的 IoC、AOP,实现了对自动生成类的动态编译 Compile。
5.1 JDK 的服务发现机制 SPI
5.1.1 简介
SPI,Service Provider Interface,服务提供者接口,是一种服务发现机制。
5.1.2 JDK 的 SPI 规范
JDK 的 SPI 规范规定:
l 接口名:可随意定义
l 实现类名:可随意定义
l 提供者配置文件路径:其查找的目录为 META-INF/services
l 提供者配置文件名称:接口的全限定性类名,没有扩展名
l 提供者配置文件内容:该接口的所有实现类的全限类性类名写入到该文件中,一个类名占一行
5.1.3 代码举例 12-jdkspi
(1) 定义工程
创建一个 Maven 的 Java 工程。
(2) 修改 pom 文件
指定编译器的源与目标版本。
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> </plugin> </plugins> </build> |
---|
(3) 定义接口
(4) 定义两个实现类
(5) 创建目录与配置文件
5.2 Dubbo 的服务发现机制 SPI
Dubbo 并没有直接使用 JDK 的 SPI,而是在其基础之上对其进行了改进。
5.2.1 规范说明
Dubbo 的 SPI 规范是:
l 接口名:可以随意定义
l 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串
l 提供者配置文件路径:在依次查找的目录为
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
l 提供者配置文件名称:接口的全限定性类名,无需扩展名
l 提供者配置文件内容:文件的内容为 key=value 形式,value 为该接口的实现类的全限类性类名,key 可以随意,但一般为该实现类的“标识前辍”(首字母小写)。一个类名占一行。
l 提供者加载:ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader 类,用于加载提供者配置文件中所有的实现类,并创建相应的实例。
5.2.2 Dubbo 的 SPI 举例 13-dubbospi
下面将实现一个下单功能。其支付方式仅支持支付宝或微信两种方式。即这里要定义一个 SPI 接口,其存在两个扩展类。
(1) 创建工程
创建一个 Maven 的 Java 工程。
(2) 导入依赖
导入 Dubbo 的依赖。
| |
| <properties> |
| —- |
project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> maven.compiler.source>1.8</maven.compiler.source> maven.compiler.target>1.8</maven.compiler.target> > > 依赖—> > >org.apache.dubbo</groupId> >dubbo</artifactId> >2.7.3</version> > > >junit</groupId> >junit</artifactId> >4.11</version> test</scope> > > |
|
---|---|
< < < </properties <dependencies <!—Dubbo <dependency <groupId <artifactId <version </dependency <dependency <groupId <artifactId <version <scope> </dependency </dependencies |
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
(3) 定义 SPI 接口
(4) 定义两个扩展类
(5) 定义扩展类配置文件
5.3 自适应机制 Adaptive
Adaptive 机制,即扩展类的自适应机制。即其可以指定想要加载的扩展名,也可以不指定。若不指定,则直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive 注解实现的。
5.3.1 @Adaptive 注解
@Adaptive 注解可以修饰类与方法,其作用相差很大。
(1) @Adaptive 修饰类
被@Adapative 修饰的 SPI 接口扩展类称为 Adaptive 类,表示该 SPI 扩展类会按照该类中指定的方式获取,即用于固定实现方式。其是装饰者设计模式的应用。
(2) @Adaptive 修饰方法
被@Adapative 修饰 SPI 接口中的方法称为 Adaptive 方法。在 SPI 扩展类中若没有找到
Adaptive 类,但系统却发现了 Adapative 方法,就会根据 Adaptive 方法自动为该 SPI 接口动态生成一个 Adaptive 扩展类,并自动将其编译。例如 Protocol 接口中就包含两个 Adaptive 方法。
5.3.2 Adaptive 类代码举例 14-adaptiveclass
(1) 创建工程
复制 13-dubbospi 工程,在其基础之上修改。
(2) 定义 adaptive 扩展类
(4) 测试 1
5.3.3 Adaptive 方法规范
下面我们准备要定义 Adaptive 方法。那么 Adaptive 方法的定义有什么要求呢?我们通过查看动态生成的 Adaptive 类来总结 Adaptive 方法的要求。
(1) 动态生成 Adaptive 类格式
package public class SPI 接口名$Adpative implements SPI 接口 { public adaptiveMethod (arg0, arg1, …) { // 注意,下面的判断仅对 URL 类型,或可以获取到 URL 类型值的参数进行判断 // 例如,dubbo 的 Invoker 类型中就包含有 URL 属性 if(arg1==null) throw new IllegalArgumentException(异常信息); if(arg1.getUrl()==null) throw new IllegalArgumentException(异常信息); URL url = arg1.getUrl(); // 其会根据@Adaptive 注解上声明的 Key 的顺序,从 URL 获取 Value, // 作为实际扩展类。若有默认扩展类,则获取默认扩展类名;否则获取 // 指定扩展名名。 String extName = url.get 接口名() == null?默认扩展前辍名:url.get 接口名(); if(extName==null) throw new IllegalStateException(异常信息); SPI 接口 extension = ExtensionLoader.getExtensionLoader(SPI 接口.class) .getExtension(extName); return extension. adaptiveMethod(arg0, arg1, …); } public unAdaptiveMethod( arg0, arg1, …) { throw new UnsupportedOperationException(异常信息); } } |
---|
(2) 方法规范
从前面的动态生成的 Adaptive 类中的 adaptiveMethod()方法体可知,其对于要加载的扩展名的指定方式是通过 URL 类型的方法参数指定的。所以对于 Adaptive 方法的定义规范仅一条:其参数包含 URL 类型的参数,或参数可以获取到 URL 类型的值。方法调用者是通过
URL 传递要加载的扩展名的。
5.3.4 Adaptive 方法代码举例 14-adaptivemethod
(1) 创建工程
复制 14-adaptiveclass 工程,在其基础之上修改。
(3) 修改两个扩展类
若存在 Adaptive 类,即使定义了 Adaptive 方法,其执行的也是 Adaptive 类,所以这里要首先将 Adaptive 类删除。
(5) 定义扩展类配置文件
由于 Adaptive 类已经删除,所以在配置文件中也需要将 Adaptive 类的注册也删除。
5.4 包装机制 Wrapper
Wrapper 机制,即扩展类的包装机制。就是对扩展类中的 SPI 接口方法进行增强,进行包装,是 AOP 思想的体现,是 Wrapper 设计模式(装饰者设计模式)的应用。一个 SPI 可以包含多个 Wrapper。
5.4.1 Wrapper 类规范
Wrapper 机制不是通过注解实现的,而是通过一套 Wrapper 规范实现的。
Wrapper 类在定义时需要遵循如下规范。
l 该类要实现 SPI 接口
l 该类中要有 SPI 接口的引用
l 该类中 SPI 接口实例是通过仅包含一个 SPI 接口参数的带参构造器传的
l 在接口实现方法中要调用 SPI 接口引用对象的相应方法
l 该类名称以 Wrapper 结尾
5.4.2 代码举例 15-wrapper
复制 14-adaptivemethod 工程,在其基础之上修改。
(2) 修改扩展类配置文件
将这两个 wrapper 注册到扩展类配置文件中。
5.5 激活机制 Activate
用于激活扩展类的。
Activate 机制,即扩展类的激活机制。通过指定的条件来激活当前的扩展类。其是通过
@Active 注解实现的。
5.5.1 @Activate 注解
在@Activate 注解中共有五个属性,其中 before、after 两个属性已经过时,剩余有效属性还有三个。它们的意义为:
l group:为扩展类指定所属的组别,是当前扩展类的一个标识。String[]类型,表示一个扩展类可以属于多个组。
l value:为当前扩展类指定的 key,是当前扩展类的一个标识。String[]类型,表示一个扩展类可以有多个指定的 key。
l order:指定筛选条件相同的扩展类的加载顺序。序号越小,优先级越高。默认值为 0。
5.5.2 代码举例
(1) 创建工程
复制 13-dubbospi 工程,在其基础之上修改。
(2) 修改扩展类 AlipayOrder
(3)修改扩展类 WeChatOrder
(4) 定义扩展类 CardOrder
(5)定义扩展类 CashOrder
(6) 定义扩展类 CouponOrder
5.6 总结
l 在配置文件中可能会存在四种扩展类:直接扩展类、Adaptive 类、Wrapper 类,及 Activate 类。它们的共同特点是,都实现了 SPI 接口。
l Adaptive 类与 Activate 类是通过注解定义的,而 Wrapper 则是通过一套规范定义的。
l 一个 SPI 接口中只会有一个 Adaptive 类(无论是否是自动生成的),而 Wrapper 类与
Activate 类则可以有多个。
l 只有 Activate 类属于直接扩展类,Adaptive 类与 Wrapper 类均不属于直接扩展类范畴,因为它们都无法独立使用,均依赖于直接扩展类。
5.7 Dubbo 的 SPI 源码解析
下面以 Protocol 扩展实例的获取过程为例来解析 SPI 的执行过程。
(1) factory 实例的 loader 的 extensionFactory 实例是 null
这个 objectFactory 实例也是通过 SPI 获取到的。
(2) 获取 factory 的 extensionLoader
构造器执行完毕,则 ExtensionFactory 的 extensionLoader 实例就创建完毕。重新返回其调用语句。
(3) 创建 Container 的 loader 的 extensionFactory
跟踪执行 getAdaptiveExtension()。
跟踪 cacheWrapperClass()方法。
重新返回 loadClass()方法。
跟踪 cacheName()方法。
可以看出,其是将当前 name 与扩展类配对后放到了 extensionClasses 中了。
(4) 获取 Protocol 的 extensionLoader
前面的代码执行完毕,然后一层层的返回,最终就返回到了这里。
此时 Protocol 的 ExtensionLoader 的 objectFactory 实例就创建完毕。返回其调用语句。
最后第 131 行返回这个新创建的 loader,返回给该 getExtensionLoader()方法的调用语句。
(5) 获取 Protocol 的默认扩展类实例
此时再跟踪第 121 行的 getAdaptiveExtension()方法,是通过刚才获取到的 Protocol 的 laoder 对象获取 Protocol 的自适合实例。其执行过程与前面的相同。
5.8 插件的 IoC 注入源码解析
5.8.1 第一次注入场景分析
在应用启动时系统会尝试着从配置中心获取动态配置,为应用启动准备运行环境。若没有指定配置中心,其默认使用注册中心作为配置中心。
若要获取动态配置,就需要获取到 ZookeeperDynamicConfigurationFactory 动态配置工厂实例。而这个实例是从 zk 配置中心中获取的动态配置。若其要读取 zk 中的数据,则该实例必须具有 zk 的客户端引用。而这个 zk 的客户端就是 ZookeeperTransporter 实例。其为一个 SPI 接口,默认扩展名为 curator。
5.8.3 injectExtension()
跟踪第 566 行的 getExtension()方法。
5.9 插件的 AOP 包装源码解析
Dubbo 的 AOP 是对 SPI 扩展类进行增强的方式,而 Wrapper 机制就是对 SPI 扩展类的增强。不同 SPI 的不同 Wrapper,其增强的功能不同。为了方便大家的理解,我们就以前面在学习“Wrapper 机制”时写的代码 15-wrapper 为例进行源码解析,来查看扩展类是如何被包装起来的。
5.9.1 找到 AOP 源码
逐级返回,直至 Order@Adaptive 类。
5.10 自动生成类的动态编译源码解析
当一个SPI接口没有Adaptive类时,系统会根据Adaptive方法为其自动生成一个Adaptive 类,这个自动生成的类是一个 java 代码类,这个类是需要编译的。而该编译是由系统动态完成的。
5.10.1 Javassist 简介
Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。一般情况下,对字节码文件进行修改是需要使用虚拟机指令的。而使用 Javassist,可以直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
也就是说,使用 javassist 可以对现有的.class 文件进行直接的编辑,修改,也可以越过.java 文件,直接创建出.class 文件。
除了实现动态编译外,javassist 通常还会用于动态代理的生成。
5.10.2 手工实现 Javassist 动态编译 17-javassist
(1) 创建工程
创建一个 Maven 的 Java 工程。
(2) 导入依赖
仅需要一个 Javassist 依赖。
<properties> | project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> maven.compiler.source>1.8</maven.compiler.source> maven.compiler.target>1.8</maven.compiler.target> > > 依赖 —> > >org.javassist</groupId> >javassist</artifactId> >3.26.0-GA</version> > > |
|
---|---|---|
< < < </properties <dependencies <!— javassist <dependency <groupId <artifactId <version </dependency </dependencies |
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
</build>
(3) 定义 JavassistCompiler
5.10.3 解析 Dubbo 的动态编译
下面以获取 Protocol 接口的 adaptive 扩展类实例为例进行解析。
此时查看 code 的值,其为一个字符串,其内容就是动态生成的 Adaptive 扩展类。
在其上右击,选择 copy value 即可复制该字符串的内容。
动态生成 Adaptive 代码后,就可以对其进行编译了。