Skip to content

dubbo 源码

litter-fish edited this page Dec 13, 2019 · 4 revisions

Dubbo SPI

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能 dubbo SPI.jpg

服务暴露

Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。 整个逻辑大致可分为三个部分, 第一部分是前置工作,主要用于检查参数,组装 URL。 第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。 第三部分是向注册中心注册服务,用于服务发现。 dubbo 服务暴露过程 中间handler的关系 handler

dubbo 服务暴露.jpg

最终导出的export export

服务引用

Dubbo 服务引用的时机: 第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务 第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的 第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 dubbo:reference 的 init 属性开启。 下面我们按照 Dubbo 默认配置进行分析,整个分析过程从 ReferenceBean 的 getObject 方法开始。当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。按照惯例,在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式,有三种, 第一种是引用本地 (JVM) 服务, 第二是通过直连方式引用远程服务, 第三是通过注册中心引用远程服务。 不管是哪种引用方式,最后都会得到一个 Invoker 实例。 如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。 服务引用

集群容错

cluster.jpg

集群工作过程: 第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。 第二个阶段是在服务消费者进行远程调用时 容错方式: dubbo 集群容错.jpg

负载均衡

LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载“均摊”到不同的机器上。避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况。通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载。在为高负载服务器分流的同时,还可以避免资源浪费,一举两得。负载均衡可分为软件负载均衡和硬件负载均衡。 Dubbo 需要对服务消费者的调用请求进行分配,避免少数服务提供者负载过大。服务提供者负载过大,会导致部分请求超时。因此将负载均衡到每个服务提供者上,是非常必要的。Dubbo 提供了4种负载均衡实现,分别是基于权重随机算法的 RandomLoadBalance、基于最少活跃调用数算法的 LeastActiveLoadBalance、基于 hash 一致性的 ConsistentHashLoadBalance,以及基于加权轮询算法的 RoundRobinLoadBalance

RandomLoadBalance 加权随机算法: 假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A 即可。 LeastActiveLoadBalance 最小活跃数负载均衡, 基于加权最小活跃数算法实现的: 活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者。 在具体实现中,每个服务提供者对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求 dubbo 负载均衡.jpg

ConsistentHashLoadBalance:

首先根据 ip 或者其他的信息为缓存节点生成一个 hash,并将这个 hash 投射到 [0, 232 - 1] 的圆环上。当有查询或写入请求时,则为缓存项的 key 生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。 consistent-hash.jpg

一致性 hash 在 Dubbo 中的应用 consistent-hash-invoker.jpg 这里相同颜色的节点均属于同一个服务提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160。这样做的目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜问题。所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。 consistent-hash-data-incline (1).jpg

RoundRobinLoadBalance: 加权轮询负载均衡 所谓轮询是指将请求轮流分配给每台服务器。 举个例子,我们有三台服务器 A、B、C。我们将第一个请求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给服务器 C,第四个请求再次分配给服务器 A。这个过程就叫做轮询。 轮询是一种无状态负载均衡算法,实现简单,适用于每台服务器性能相近的场景下。 现实中每台服务器性能不一样,处理速度也不同,因此, 对轮询过程进行加权,以调控每台服务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。 比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。

平滑加权轮询: 每个Invoker拥有两个参数: weight:invoker提供的权重,该值一直不会变化 current:invoker的当前权重,该值会随着调用次数而改变 算法步骤:

  1. 初始状态invoker的current = 0
  2. 迭代invoker列表,赋值current += weight,并计算权重和sumWeight
  3. 找出current最大的invoker
  4. 计算invoker的current, current -= sumWeight
  5. 循环2 - 4步骤 例如: invokers:初始weight分别为,5,1,1
请求  选中前current  invoker下标  选中后current    weight
1     {5,1,1}          0       {5-7,1,1}    {5,1,1}
2     {3,2, 2}         0       {3-7,2,2}    {5,1,1}
3     {1,3,3}          1       {1,3-7,3}    {5,1,1}
4     {6,-3,4}         0       {6-7,-3,4}   {5,1,1}
5     {4,-2,5}         2       {4,-2,5-7}   {5,1,1}
6     {9,-1,-1}        0       {9-7,-1,-1}  {5,1,1}
7     {7,0,0}          0       {0,0,0}      {5,1,1}
8     {5,1,1}          0       {5-7,1,1}    {5,1,1}

dubbo 负载均衡2.jpg

服务调用过程

请求发送过程 dubbo 调用过程 - 请求发送过程.jpg

请求接受过程及返回结果、消费端接受处理结果 dubbo 调用过程 - 请求接受过程.jpg

Clone this wiki locally