一、架构整体设计

1.Dubbo调用关系说明

dubbo原理及源码剖析_服务提供者

在这里主要由四部分组成:

Provider: 暴露服务的服务提供方
Protocol 负责提供者和消费者之间协议交互数据
Service 真实的业务服务信息 可以理解成接口和实现
Container Dubbo的运行环境
Consumer: 调用远程服务的服务消费方
Protocol 负责提供者和消费者之间协议交互数据
Cluster 感知提供者端的列表信息
Proxy 可以理解成 提供者的服务调用代理类 由它接管 Consumer中的接口调用逻辑
Registry: 注册中心,用于作为服务发现和路由配置等工作,提供者和消费者都会在这里进行注册
Monitor: 用于提供者和消费者中的数据统计,比如调用频次,成功失败次数等信息。
启动和执行流程说明:
提供者端启动 容器负责把Service信息加载 并通过Protocol 注册到注册中心
消费者端启动 通过监听提供者列表来感知提供者信息 并在提供者发生改变时 通过注册中心及时通知消费端
消费方发起 请求 通过Proxy模块
利用Cluster模块 来选择真实的要发送给的提供者信息
交由Consumer中的Protocol 把信息发送给提供者
提供者同样需要通过 Protocol 模块来处理消费者的信息
最后由真正的服务提供者 Service 来进行处理
2.整体的调用链路

dubbo原理及源码剖析_本地缓存_02

说明 淡绿色代表了 服务生产者的范围 淡蓝色 代表了服务消费者的范围 红色箭头代表了调用的方向
业务逻辑层 RPC层(远程过程调用) Remoting (远程数据传输)
整体链路调用的流程:
1. 消费者通过Interface进行方法调用 统一交由消费者端的 Proxy 通过ProxyFactory 来进行代理对象的创建 使用到了 jdk javassist技术
2.交给Filter 这个模块 做一个统一的过滤请求 在SPI案例中涉及过
3.接下来会进入最主要的Invoker调用逻辑
通过Directory 去配置中新读取信息 最终通过list方法获取所有的Invoker
通过Cluster模块 根据选择的具体路由规则 来选取Invoker列表
通过LoadBalance模块 根据负载均衡策略 选择一个具体的Invoker 来处理我们的请求
如果执行中出现错误 并且Consumer阶段配置了重试机制 则会重新尝试执行
4. 继续经过Filter 进行执行功能的前后封装 Invoker 选择具体的执行协议
5. 客户端 进行编码和序列化 然后发送数据
6. 到达Consumer中的 Server 在这里进行 反编码 和 反序列化的接收数据
7. 使用Exporter选择执行器
8. 交给Filter 进行一个提供者端的过滤 到达 Invoker 执行器
9. 通过Invoker 调用接口的具体实现 然后返回

 二、服务注册与消费

1.服务的注册过程分析

dubbo原理及源码剖析_ide_03

首先 ServiceConfig 类拿到对外提供服务的实际类 ref(如:HelloServiceImpl),然后通过ProxyFactory 接口实现类中的 getInvoker 方法使用 ref 生成一个 AbstractProxyInvoker 实例,到这一步就完成具体服务到 Invoker 的转化。接下来就是 Invoker 转换到 Exporter 的过程。

Invoker 转换成 Exporter的过程
其中会涉及到 RegistryService接口 RegistryFactory 接口 和 注册provider到注册中心流程的过程
(1)RegistryService代码解读,这块儿的代码比较简单,主要是对指定的路径进行注册,解绑,监听和取消监听,查询操作。也是注册中心中最为基础的类。
(2)我们再来看 RegistryFactory ,是通过他来生成真实的注册中心。通过这种方式,也可以保证一个应用中可以使用多个注册中心。可以看到这里也是通过不同的protocol参数,来选择不同的协议。
(3)下面我们就来跟踪一下,一个服务是如何注册到注册中心上去的。其中比较关键的一个类是RegistryProtocol ,他负责管理整个注册中心相关协议。并且统一对外提供服务。这里我们主要以RegistryProtocol.export 方法作为入口,这个方法主要的作用就是将我们需要执行的信息注册并且导出。
(4)下面我们再来看看 register 方法, 这里面做的比较简单,主要是从 RegistoryFactory 中获取注册中心,并且进行地址注册。
(5)这里我们再跟里面的register方法之前,先来介绍一下Registry中的类目录结构
+- RegistryService
| +- Registry
| | +- AbstractRegistry
| | | +- FailbackRegistry
| | | | +- ZookeeperRegistry
| | | | +- NacosRegistry
| | | | +- ...
目录结构描述如下:
在这里每个层级代表继承自父级
这里面 RegistryService 就是我们之前所讲对外提供注册机制的接口。
其下面 Registry 也同样是一个接口,是对 RegistryService 的集成,并且继承了 Node 接口,说明注册中心也是基于URL去做的。
AbstractRegistry 是对注册中心的封装,其主要会对本地注册地址的封装,主要功能在于远程注册中心不可用的时候,可以采用本地的注册中心来使用。
FailbackRegistry 从名字中可以看出来,失败自动恢复,后台记录失败请求,定时重发功能。
最深的一层则更多是真实的第三方渠道实现。
(6)下面我们来看一下在 FailbackRegistry 中的实现, 可以在这里看到他的主要作用是调用第三方的实现方式,并且在出现错误时增加重试机制。
(7)下面我们再来看看Zookeeper中 doRegister 方法的实现, 可以看到这里的实现也比较简单,关键在于 toUrlPath 方法的实现。关于 dynamic 的值,我们也在上面有看到,他的URL也是true的。
(8)解读 toUrlPath 方法。可以看到这里的实现也是比较简单,也验证了我们之前的路径规2. URL规则详解 和 服务本地缓存1 URL规则详解

URL地址如下:
protocol://host:port/path?key=value&key=value
provider://192.168.20.1:20883/com.xxx.service.HelloService?
anyhost=true&application=serviceprovider2&bind.ip=192.168.20.1&bind.port=20883&category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.xxx.service
URL主要有以下几部分组成:
protocol: 协议,一般像我们的 provider 或者 consumer 在这里都是人为具体的协议
host: 当前 provider 或者其他协议所具体针对的地址,比较特殊的像 override 协议所指定的host就是 0.0.0.0 代表所有的机器都生效
port: 和上面相同,代表所处理的端口号
path: 服务路径,在 provider 或者 consumer 等其他中代表着我们真实的业务接口
key=value: 这些则代表具体的参数,这里我们可以理解为对这个地址的配置。比如我们 provider中需要具体机器的服务应用名,就是一个配置的方式设置上去。
注意:Dubbo中的URL与java中的URL是有一些区别的,如下:
这里提供了针对于参数的 parameter 的增加和减少(支持动态更改)
提供缓存功能,对一些基础的数据做缓存.
2 服务本地缓存
dubbo调用者需要通过注册中心(例如:ZK)注册信息,获取提供者,但是如果频繁往从ZK获取信息,肯定会存在单点故障问题,所以dubbo提供了将提供者信息缓存在本地的方法。
Dubbo在订阅注册中心的回调处理逻辑当中会保存服务提供者信息到本地缓存文件当中(同步/异步两种方式),以URL纬度进行全量保存。
Dubbo在服务引用过程中会创建registry对象并加载本地缓存文件,会优先订阅注册中心,订阅注册中心失败后会访问本地缓存文件内容获取服务提供信息。

3. Dubbo 消费过程分析

服务消费流程

dubbo原理及源码剖析_服务提供者_04

首先 ReferenceConfig 类的 init 方法调用 createProxy() ,期间 使用 Protocol 调用 refer 方法生成 Invoker 实例(如上图中的红色部分),这是服务消费的关键。接下来使用ProxyFactory把 Invoker转换为客户端需要的接口(如:HelloService)。

三、集群容错

dubbo原理及源码剖析_本地缓存_05

集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。
第二个阶段是在服务消费者进行远程调用时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举Invoker 列表(可将 Invoker 简单理解为服务提供者)。Directory 的用途是保存 Invoker列表,可简单类比为 List。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker实例的 invoke 方法,进行真正的远程调用。

Dubbo 主要提供了这样几种容错方式:
Failover Cluster - 失败自动切换 失败时会重试其它服务器
Failfast Cluster - 快速失败 请求失败后快速返回异常结果 不重试
Failsafe Cluster - 失败安全 出现异常 直接忽略 会对请求做负载均衡
Failback Cluster - 失败自动恢复 请求失败后 会自动记录请求到失败队列中
Forking Cluster - 并行调用多个服务提供者 其中有一个返回 则立即返回结果