开始对Dubbo源码有点兴趣,加入了《芋道源码》跟大家一起学。
现在的版本是 2.7.0
1. 依赖工具
-
Maven
-
Git
-
JDK
-
IntelliJ IDEA
2. 源码拉取
从官方仓库 https://github.com/apache/incubator-dubbo Fork
出属于自己的仓库https://github.com/sxyseo/incubator-dubbo。为什么要 Fork
?既然开始阅读、调试源码,我们可能会写一些注释,有了自己的仓库,可以进行自由的提交。😈
使用 IntelliJ IDEA
从 Fork
出来的仓库拉取代码。拉取完成后,Maven
会下载依赖包,可能会花费一些时间,耐心等待下。
Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
-
Registry 注册中心,用于服务的注册与发现。
-
Provider 服务提供者,通过向 Registry 注册服务。
-
Consumer 服务消费者,通过从 Registry 发现服务。
另外,Consumer 对 Provider 的调用,直接发起
框架设计
整体设计
图例说明:
-
图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
-
图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
-
图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
-
图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。
各层说明
-
config 配置层:对外配置接口,以
ServiceConfig
,ReferenceConfig
为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类 -
proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以
ServiceProxy
为中心,扩展接口为ProxyFactory
-
registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为
RegistryFactory
,Registry
,RegistryService
-
cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以
Invoker
为中心,扩展接口为Cluster
,Directory
,Router
,LoadBalance
-
monitor 监控层:RPC 调用次数和调用时间监控,以
Statistics
为中心,扩展接口为MonitorFactory
,Monitor
,MonitorService
-
protocol 远程调用层:封装 RPC 调用,以
Invocation
,Result
为中心,扩展接口为Protocol
,Invoker
,Exporter
-
exchange 信息交换层:封装请求响应模式,同步转异步,以
Request
,Response
为中心,扩展接口为Exchanger
,ExchangeChannel
,ExchangeClient
,ExchangeServer
-
transport 网络传输层:抽象 mina 和 netty 为统一接口,以
Message
为中心,扩展接口为Channel
,Transporter
,Client
,Server
,Codec
-
serialize 数据序列化层:可复用的一些工具,扩展接口为
Serialization
,ObjectInput
,ObjectOutput
,ThreadPool
关系说明
-
在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上 Filter 拦截点。
-
图中的 Consumer 和 Provider 是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用 Client 和 Server 的原因是 Dubbo 在很多场景下都使用 Provider, Consumer, Registry, Monitor 划分逻辑拓普节点,保持统一概念。
-
而 Cluster 是外围概念,所以 Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样其它人只要关注 Protocol 层 Invoker 即可,加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。
-
Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将 Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以 Run 的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
-
而 Remoting 实现是 Dubbo 协议的实现,如果你选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina, Netty, Grizzly 的抽象,它也可以扩展 UDP 传输,而 Exchange 层是在传输层之上封装了 Request-Response 语义。
-
Registry 和 Monitor 实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。
模块分包
模块说明:
-
dubbo-common 公共逻辑模块:包括 Util 类和通用模型。
-
dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。
-
dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。
-
dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
-
dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
-
dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
-
dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用Dubbo,隐藏 Dubbo 所有细节。
-
dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。
整体上按照分层结构进行分包,与分层的不同点在于:
-
container 为服务容器,用于部署运行服务,没有在层中画出。
-
protocol 层和 proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。
-
transport 层和 exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础。
-
serialize 层放在 common 模块中,以便更大程度复用。
依赖关系
图例说明:
-
图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。
-
图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。
-
图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。
-
图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。
调用链
展开总设计图的红色调用链,如下:
暴露服务时序
展开总设计图左边服务提供方暴露服务的蓝色初始化链,时序图如下:
引用服务时序
展开总设计图右边服务消费方引用服务的蓝色初始化链,时序图如下:
领域模型
在 Dubbo 的核心领域模型中:
-
Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
-
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
-
Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。
基本设计原则
-
采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。
-
采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
源码构建
代码签出
通过以下的这个命令签出最新的项目源码 [1]:
git clone https://github.com/apache/incubator-dubbo.git dubbo
分支
我们使用 master 作为主干版本的开发,使用分支作为维护版本。可以通过 https://github.com/apache/incubator-dubbo/tags 来查看所有版本的标签。
构建
Dubbo 使用 maven 作为构建工具。
要求
-
Java 1.5 以上的版本
-
Maven 2.2.1 或者以上的版本
构建之前需要配置以下的 MAVEN_OPTS
export MAVEN_OPTS=-Xmx1024m -XX:MaxPermSize=512m
使用以下命令做一次构建
mvn clean install
可以通过以下的构建命令来跳过单元测试
mvn install -Dmaven.test.skip
构建源代码 jar 包
通过以下命令以构建 Dubbo 的源代码 jar 包,方便用来调试 Dubbo 源代码
mvn clean source:jar install -Dmaven.test.skip
IDE 支持
使用以下命令来生成 IDE 的工程
Intellij Idea
mvn idea:idea
eclipse
mvn eclipse:eclipse
在 eclipse 中导入
首先,需要在 eclipse 中配置 maven 仓库。通过 Preferences -> Java -> Build Path -> Classpath 定义 M2_REPO
的 classpath 变量指向本地的 maven 仓库。 [2]
也可以通过以下的 maven 命令配置:
mvn eclipse:configure-workspace -Declipse.workspace=/path/to/the/workspace/
功能成熟度
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
并发控制 | Tested | 并发控制 | 试用 | ||
连接控制 | Tested | 连接数控制 | 试用 | ||
直连提供者 | Tested | 点对点直连服务提供方,用于测试 | 测试环境使用 | Alibaba | |
分组聚合 | Tested | 分组聚合返回值,用于菜单聚合等服务 | 特殊场景使用 | 可用于生产环境 | |
参数验证 | Tested | 参数验证,JSR303验证框架集成 | 对性能有影响 | 试用 | LaiWang |
结果缓存 | Tested | 结果缓存,用于加速请求 | 试用 | ||
泛化引用 | Stable | 泛化调用,无需业务接口类进行远程调用,用于测试平台,开放网关桥接等 | 可用于生产环境 | Alibaba | |
泛化实现 | Stable | 泛化实现,无需业务接口类实现任意接口,用于Mock平台 | 可用于生产环境 | Alibaba | |
回声测试 | Tested | 回声测试 | 试用 | ||
隐式传参 | Stable | 附加参数 | 可用于生产环境 | ||
异步调用 | Tested | 不可靠异步调用 | 试用 | ||
本地调用 | Tested | 本地调用 | 试用 | ||
参数回调 | Tested | 参数回调 | 特殊场景使用 | 试用 | Registry |
事件通知 | Tested | 事件通知,在远程调用执行前后触发 | 试用 | ||
本地存根 | Stable | 在客户端执行部分逻辑 | 可用于生产环境 | Alibaba | |
本地伪装 | Stable | 伪造返回结果,可在失败时执行,或直接执行,用于服务降级 | 需注册中心支持 | 可用于生产环境 | Alibaba |
延迟暴露 | Stable | 延迟暴露服务,用于等待应用加载warmup数据,或等待spring加载完成 | 可用于生产环境 | Alibaba | |
延迟连接 | Tested | 延迟建立连接,调用时建立 | 试用 | Registry | |
粘滞连接 | Tested | 粘滞连接,总是向同一个提供方发起请求,除非此提供方挂掉,再切换到另一台 | 试用 | Registry | |
令牌验证 | Tested | 令牌验证,用于服务授权 | 需注册中心支持 | 试用 | |
路由规则 | Tested | 动态决定调用关系 | 需注册中心支持 | 试用 | |
配置规则 | Tested | 动态下发配置,实现功能的开关 | 需注册中心支持 | 试用 | |
访问日志 | Tested | 访问日志,用于记录调用信息 | 本地存储,影响性能,受磁盘大小限制 | 试用 | |
分布式事务 | Research | JTA/XA三阶段提交事务 | 不稳定 | 不可用 |
策略成熟度
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Zookeeper注册中心 | Stable | 支持基于网络的集群方式,有广泛周边开源产品,建议使用dubbo-2.3.3以上版本(推荐使用) | 依赖于Zookeeper的稳定性 | 可用于生产环境 | |
Redis注册中心 | Stable | 支持基于客户端双写的集群方式,性能高 | 要求服务器时间同步,用于检查心跳过期脏数据 | 可用于生产环境 | |
Multicast注册中心 | Tested | 去中心化,不需要安装注册中心 | 依赖于网络拓扑和路由,跨机房有风险 | 小规模应用或开发测试环境 | |
Simple注册中心 | Tested | Dogfooding,注册中心本身也是一个标准的RPC服务 | 没有集群支持,可能单点故障 | 试用 | |
Feature | Maturity | Strength | Problem | Advise | User |
Simple监控中心 | Stable | 支持JFreeChart统计报表 | 没有集群支持,可能单点故障,但故障后不影响RPC运行 | 可用于生产环境 | |
Feature | Maturity | Strength | Problem | Advise | User |
Dubbo协议 | Stable | 采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用) | 在大文件传输时,单一连接会成为瓶颈 | 可用于生产环境 | Alibaba |
Rmi协议 | Stable | 可与原生RMI互操作,基于TCP协议 | 偶尔会连接失败,需重建Stub | 可用于生产环境 | Alibaba |
Hessian协议 | Stable | 可与原生Hessian互操作,基于HTTP协议 | 需hessian.jar支持,http短连接的开销大 | 可用于生产环境 | |
Feature | Maturity | Strength | Problem | Advise | User |
Netty Transporter | Stable | JBoss的NIO框架,性能较好(推荐使用) | 一次请求派发两种事件,需屏蔽无用事件 | 可用于生产环境 | Alibaba |
Mina Transporter | Stable | 老牌NIO框架,稳定 | 待发送消息队列派发不及时,大压力下,会出现FullGC | 可用于生产环境 | Alibaba |
Grizzly Transporter | Tested | Sun的NIO框架,应用于GlassFish服务器中 | 线程池不可扩展,Filter不能拦截下一Filter | 试用 | |
Feature | Maturity | Strength | Problem | Advise | User |
Hessian Serialization | Stable | 性能较好,多语言支持(推荐使用) | Hessian的各版本兼容性不好,可能和应用使用的Hessian冲突,Dubbo内嵌了hessian3.2.1的源码 | 可用于生产环境 | Alibaba |
Dubbo Serialization | Tested | 通过不传送POJO的类元信息,在大量POJO传输时,性能较好 | 当参数对象增加字段时,需外部文件声明 | 试用 | |
Json Serialization | Tested | 纯文本,可跨语言解析,缺省采用FastJson解析 | 性能较差 | 试用 | |
Java Serialization | Stable | Java原生支持 | 性能较差 | 可用于生产环境 | |
Feature | Maturity | Strength | Problem | Advise | User |
Javassist ProxyFactory | Stable | 通过字节码生成代替反射,性能比较好(推荐使用) | 依赖于javassist.jar包,占用JVM的Perm内存,Perm可能要设大一些:java -XX:PermSize=128m | 可用于生产环境 | Alibaba |
Jdk ProxyFactory | Stable | JDK原生支持 | 性能较差 | 可用于生产环境 | |
Feature | Maturity | Strength | Problem | Advise | User |
Failover Cluster | Stable | 失败自动切换,当出现失败,重试其它服务器,通常用于读操作(推荐使用) | 重试会带来更长延迟 | 可用于生产环境 | Alibaba |
Failfast Cluster | Stable | 快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作 | 如果有机器正在重启,可能会出现调用失败 | 可用于生产环境 | Alibaba |
Failsafe Cluster | Stable | 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作 | 调用信息丢失 | 可用于生产环境 | Monitor |
Failback Cluster | Tested | 失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作 | 不可靠,重启丢失 | 可用于生产环境 | Registry |
Forking Cluster | Tested | 并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作 | 需要浪费更多服务资源 | 可用于生产环境 | |
Broadcast Cluster | Tested | 广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于更新提供方本地状态 | 速度慢,任意一台报错则报错 | 可用于生产环境 | |
Feature | Maturity | Strength | Problem | Advise | User |
Random LoadBalance | Stable | 随机,按权重设置随机概率(推荐使用) | 在一个截面上碰撞的概率高,重试时,可能出现瞬间压力不均 | 可用于生产环境 | Alibaba |
RoundRobin LoadBalance | Stable | 轮询,按公约后的权重设置轮询比率 | 存在慢的机器累积请求问题,极端情况可能产生雪崩 | 可用于生产环境 | |
LeastActive LoadBalance | Stable | 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的机器收到更少请求 | 不支持权重,在容量规划时,不能通过权重把压力导向一台机器压测容量 | 可用于生产环境 | |
ConsistentHash LoadBalance | Stable | 一致性Hash,相同参数的请求总是发到同一提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动 | 压力分摊不均 | 可用于生产环境 | |
Feature | Maturity | Strength | Problem | Advise | User |
条件路由规则 | Stable | 基于条件表达式的路由规则,功能简单易用 | 有些复杂多分支条件情况,规则很难描述 | 可用于生产环境 | Alibaba |
脚本路由规则 | Tested | 基于脚本引擎的路由规则,功能强大 | 没有运行沙箱,脚本能力过于强大,可能成为后门 | 试用 | |
Feature | Maturity | Strength | Problem | Advise | User |
Spring Container | Stable | 自动加载META-INF/spring目录下的所有Spring配置 | 可用于生产环境 | Alibaba | |
Jetty Container | Stable | 启动一个内嵌Jetty,用于汇报状态 | 大量访问页面时,会影响服务器的线程和内存 | 可用于生产环境 | Alibaba |
Log4j Container | Stable | 自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录 | 用户不能控制log4j的配置,不灵活 | 可用于生产环境 | Alibaba |