1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-20 22:17:09 +08:00

Update 关于Dubbo的重要知识点.md

This commit is contained in:
guide 2021-03-26 15:45:25 +08:00
parent ccdea6cf59
commit 514e3c739a

View File

@ -1,282 +1,468 @@
本文是作者根据官方文档以及自己平时的使用情况,对 Dubbo 所做的一个总结。如果不懂 Dubbo 的使用的话,可以参考我的这篇文章[《超详细,新手都能看懂 使用SpringBoot+Dubbo 搭建一个简单的分布式服务》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484706&idx=1&sn=d413fc17023482f67ca17cb6756b9ff8&chksm=fd985343caefda555969568fdf4734536e0a1745f9de337d434a7dbd04e893bd2d75f3641aab&token=1902169190&lang=zh_CN#rd)
这篇文章是我根据官方文档以及自己平时的使用情况,对 Dubbo 所做的一个总结。欢迎补充!
Dubbo 官网http://dubbo.apache.org/zh-cn/index.html
## RPC基础
Dubbo 中文文档: http://dubbo.apache.org/zh-cn/index.html
### 何为 RPC?
<!-- MarkdownTOC -->
**RPCRemote Procedure Call** 即远程过程调用,通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。
- [一 重要的概念](#一-重要的概念)
- [1.1 什么是 Dubbo?](#11-什么是-dubbo)
- [1.2 什么是 RPC?RPC原理是什么?](#12-什么是-rpcrpc原理是什么)
- [1.3 为什么要用 Dubbo?](#13-为什么要用-dubbo)
- [1.4 什么是分布式?](#14-什么是分布式)
- [1.5 为什么要分布式?](#15-为什么要分布式)
- [二 Dubbo 的架构](#二-dubbo-的架构)
- [2.1 Dubbo 的架构图解](#21-dubbo-的架构图解)
- [2.2 Dubbo 工作原理](#22-dubbo-工作原理)
- [三 Dubbo 的负载均衡策略](#三-dubbo-的负载均衡策略)
- [3.1 先来解释一下什么是负载均衡](#31-先来解释一下什么是负载均衡)
- [3.2 再来看看 Dubbo 提供的负载均衡策略](#32-再来看看-dubbo-提供的负载均衡策略)
- [3.2.1 Random LoadBalance\(默认,基于权重的随机负载均衡机制\)](#321-random-loadbalance默认基于权重的随机负载均衡机制)
- [3.2.2 RoundRobin LoadBalance\(不推荐,基于权重的轮询负载均衡机制\)](#322-roundrobin-loadbalance不推荐基于权重的轮询负载均衡机制)
- [3.2.3 LeastActive LoadBalance](#323-leastactive-loadbalance)
- [3.2.4 ConsistentHash LoadBalance](#324-consistenthash-loadbalance)
- [3.3 配置方式](#33-配置方式)
- [四 zookeeper宕机与dubbo直连的情况](#四-zookeeper宕机与dubbo直连的情况)
<!-- /MarkdownTOC -->
**为什么要 RPC ** 因为两个不同的服务器上的服务提供的方法不在一个内存空间所以需要通过网络编程才能传递方法调用所需要的参数。并且方法调用的结果也需要通过网络编程来接收。但是如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的因为我们需要考虑底层传输方式TCP还是UDP、序列化方式等等方面。
## 一 重要的概念
**RPC 能帮助我们做什么呢? ** 简单来说,通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。并且!我们不需要了解底层网络编程的具体细节。
### 1.1 什么是 Dubbo?
Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。简单来说 Dubbo 是一个分布式服务框架致力于提供高性能和透明化的RPC远程服务调用方案以及SOA服务治理方案
举个例子:两个不同的服务 A、B 部署在两台不同的机器上,服务 A 如果想要调用服务 B 中的某个方法的话就可以通过 RPC 来做。
Dubbo 目前已经有接近 23k 的 Star Dubbo的Github 地址:[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo) 。 另外在开源中国举行的2018年度最受欢迎中国开源软件这个活动的评选中Dubbo 更是凭借其超高人气仅次于 vue.js 和 ECharts 获得第三名的好成绩。
一言蔽之:**RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。**
Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。
### RPC 的原理是什么?
**我们上面说了 Dubbo 实际上是 RPC 框架,那么什么是 RPC呢**
为了能够帮助小伙伴们理解 RPC 原理,我们可以将整个 RPC的 核心功能看作是下面👇 6 个部分实现的:
### 1.2 什么是 RPC?RPC原理是什么?
**什么是 RPC**
1. **客户端(服务消费端)** :调用远程方法的一端。
1. **客户端 Stub** 这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
1. **网络传输** 网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最近基本的 Socket或者性能以及封装更加优秀的 Netty推荐
1. **服务端 Stub** :这个桩就不是代理类了。我觉得理解为桩实际不太好,大家注意一下就好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去指定对应的方法然后返回结果给客户端的类。
1. **服务端(服务提供端)** :提供远程方法的一端。
RPCRemote Procedure Call—远程过程调用它是一种通过网络从远程计算机程序上请求服务而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求当然可以但是可能会比较麻烦。 RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。
具体原理图如下后面我会串起来将整个RPC的过程给大家说一下
**RPC原理是什么**
我这里这是简单的提一下。详细内容可以查看下面这篇文章:
[http://www.importnew.com/22003.html](http://www.importnew.com/22003.html)
![RPC原理图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-6/37345851.jpg)
1. 服务消费端client以本地调用的方式调用远程服务
1. 客户端 Stubclient stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):`RpcRequest`
1. 客户端 Stubclient stub 找到远程服务的地址,并将消息发送到服务提供端;
1. 服务端 Stub收到消息将消息反序列化为Java对象: `RpcRequest`
1. 服务端 Stub根据`RpcRequest`中的类、方法、方法参数等信息调用本地的方法;
1. 服务端 Stub得到方法执行结果并将组装成能够进行网络传输的消息体`RpcResponse`(序列化)发送至消费方;
1. 客户端 Stubclient stub接收到消息并将消息反序列化为Java对象:`RpcResponse` 这样也就得到了最终结果。over!
1. 服务消费方client调用以本地调用方式调用服务
2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体
3. client stub找到服务地址并将消息发送到服务端
4. server stub收到消息后进行解码
5. server stub根据解码结果调用本地的服务
6. 本地服务执行并将结果返回给server stub
7. server stub将返回结果打包成消息并发送至消费方
8. client stub接收到消息并进行解码
9. 服务消费方得到最终结果。
相信小伙伴们看完上面的讲解之后,已经了解了 RPC 的原理。
下面再贴一个网上的时序图:
虽然篇幅不多,但是基本把 RPC 框架的核心原理讲清楚了!另外,对于上面的技术细节,我会在后面的章节介绍到。
![RPC原理时序图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-6/32527396.jpg)
**最后,对于 RPC 的原理,希望小伙伴不单单要理解,还要能够自己画出来并且能够给别人讲出来。因为,在面试中这个问题在面试官问到 RPC 相关内容的时候基本都会碰到。**
**说了这么多,我们为什么要用 Dubbo 呢?**
## Dubbo基础
### 1.3 为什么要用 Dubbo?
### 什么是 Dubbo?
Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面向服务的架构Service Oriented Architecture也就是把工程按照业务逻辑拆分成服务层、表现层两个工程。服务层中包含业务逻辑只需要对外提供服务即可。表现层只需要处理和页面的交互业务逻辑都是调用服务层的服务来实现。SOA架构中有两个主要角色服务提供者Provider和服务使用者Consumer
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2020-8/427f2168-1930-4c14-8760-415fac8db1d0-20200802184737978.png)
![为什么要用 Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/43050183.jpg)
[Apache Dubbo](https://github.com/apache/incubator-dubbo) (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源 Java RPC 框架。
**如果你要开发分布式程序,你也可以直接基于 HTTP 接口进行通信,但是为什么要用 Dubbo呢**
根据 [Dubbo 官方文档](https://dubbo.apache.org/zh/)的介绍Dubbo 提供了六大核心能力
我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo
1. 面向接口代理的高性能RPC调用。
2. 智能容错和负载均衡。
3. 服务自动注册和发现。
4. 高度可扩展能力。
5. 运行期流量调度。
6. 可视化的服务治理与运维。
1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务。
2. **服务调用链路生成**——随着系统的发展服务越来越多服务间依赖关系变得错踪复杂甚至分不清哪个应用要在哪个应用之前启动架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。
3. **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。
4. **服务降级**——某个服务挂掉之后调用备用服务。
![Dubbo提供的六大核心能力](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/%E6%BA%90%E7%A0%81/dubbo/dubbo%E6%8F%90%E4%BE%9B%E7%9A%84%E5%85%AD%E5%A4%A7%E6%A0%B8%E5%BF%83%E8%83%BD%E5%8A%9B.png)
简单来说就是: **Dubbo 不光可以帮助我们调用远程服务,还提供了一些其他开箱即用的功能比如智能负载均衡。**
Dubbo 目前已经有接近 34.4 k 的 Star 。
**2020 年度 OSC 中国开源项目** 评选活动中Dubbo 位列开发框架和基础组件类项目的第7名。想比几年前来说热度和排名有所下降。
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/%E6%BA%90%E7%A0%81/dubbo/image-20210107153159545.png)
Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。
### 为什么要用 Dubbo?
随着互联网的发展,网站的规模越来越大,用户数量越来越多。单一应用架构 、垂直应用架构无法满足我们的需求,这个时候分布式服务架构就诞生了。
分布式服务架构下,系统被拆分成不同的服务比如短信服务、安全服务,每个服务独立提供系统的某个核心服务。
我们可以使用 Java RMIJava Remote Method Invocation、Hessian这种支持远程调用的框架来简单地暴露和引用远程服务。但是当服务越来越多之后服务调用关系越来越复杂。当应用访问压力越来越大后负载均衡以及服务监控的需求也迫在眉睫。我们可以用 F5 这类硬件来做负载均衡,但这样增加了成本,并且存在单点故障的风险。
不过Dubbo 的出现让上述问题得到了解决。**Dubbo 帮助我们解决了什么问题呢?**
1. **负载均衡** 同一个服务部署在不同的机器时该调用那一台机器上的服务。
2. **服务调用链路生成** 随着系统的发展服务越来越多服务间依赖关系变得错踪复杂甚至分不清哪个应用要在哪个应用之前启动架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。
3. **服务访问压力以及时长统计、资源调度和治理** :基于访问压力实时管理集群容量,提高集群利用率。
4. ......
![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/43050183.jpg)
另外Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。
**我们刚刚提到了分布式这个概念,下面再给大家介绍一下什么是分布式?为什么要分布式?**
### 1.4 什么是分布式?
## 分布式基础
### 什么是分布式?
分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。
### 1.5 为什么要分布式?
![分布式事务示意图](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E7%A4%BA%E6%84%8F%E5%9B%BE.png)
### 为什么要分布式?
从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。
另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?
## 二 Dubbo 的架构
## Dubbo 架构
### 2.1 Dubbo 的架构图解
### Dubbo 架构中的核心角色有哪些?
![Dubbo 架构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/46816446.jpg)
[官方文档中的框架设计章节](https://dubbo.apache.org/zh/docs/v2.7/dev/design/) 已经介绍的非常详细了,我这里把一些比较重要的点再提一下。
**上述节点简单说明:**
![dubbo-relation](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/%E6%BA%90%E7%A0%81/dubbo/dubbo-relation.jpg)
- **Provider** 暴露服务的服务提供方
- **Consumer** 调用远程服务的服务消费方
- **Registry** 服务注册与发现的注册中心
- **Monitor** 统计服务的调用次数和调用时间的监控中心
- **Container** 服务运行容器
上述节点简单介绍以及他们之间的关系:
**调用关系说明:**
- **Container** 服务运行容器,负责加载、运行服务提供者。必须。
- **Provider** 暴露服务的服务提供方,会向注册中心注册自己提供的服务。必须。
- **Consumer** 调用远程服务的服务消费方,会向注册中心订阅自己所需的服务。必须。
- **Registry** 服务注册与发现的注册中心。注册中心会返回服务提供者地址列表给消费者。非必须。
- **Monitor** 统计服务的调用次数和调用时间的监控中心。服务消费者和提供者会定时发送统计数据到监控中心。 非必须。
1. 服务容器负责启动,加载,运行服务提供者。
2. 服务提供者在启动时,向注册中心注册自己提供的服务。
3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
### Dubbo 中的 Invoker 概念了解么?
**重要知识点总结:**
`Invoker` 是 Dubbo 领域模型中非常重要的一个概念,你如果阅读过 Dubbo 源码的话,你会无数次看到这玩意。就比如下面我要说的负载均衡这块的源码中就有大量 `Invoker` 的身影。
- **注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小**
- **监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示**
- **注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外**
- **注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者**
- **注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表**
- **注册中心和监控中心都是可选的,服务消费者可以直连服务提供者**
- **服务提供者无状态,任意一台宕掉后,不影响使用**
- **服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复**
简单来说,`Invoker` 就是 Dubbo 对远程调用的抽象。
![dubbo_rpc_invoke.jpg](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/dubbo_rpc_invoke.jpg)
按照 Dubbo 官方的话来说,`Invoker` 分为
- 服务提供 `Invoker`
- 服务消费 `Invoker`
假如我们需要调用一个远程方法,我们需要动态代理来屏蔽远程调用的细节吧!我们屏蔽掉的这些细节就依赖对应的 `Invoker` 实现, `Invoker` 实现了真正的远程服务调用。
### Dubbo 的工作原理了解么?
下图是 Dubbo 的整体设计,从下至上分为十层,各层均为单向依赖。
> 左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
![dubbo-framework](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/source-code/dubbo/dubbo-framework.jpg)
- **config 配置层**Dubbo相关的配置。支持代码配置同时也支持基于 Spring 来做配置,以 `ServiceConfig`, `ReferenceConfig` 为中心
- **proxy 服务代理层**:调用远程方法像调用本地的方法一样简单的一个关键,真实调用过程依赖代理类,以 `ServiceProxy` 为中心。
- **registry 注册中心层**:封装服务地址的注册与发现。
- **cluster 路由层**:封装多个提供者的路由及负载均衡,并桥接注册中心,以 `Invoker` 为中心。
- **monitor 监控层**RPC 调用次数和调用时间监控,以 `Statistics` 为中心。
- **protocol 远程调用层**:封装 RPC 调用,以 `Invocation`, `Result` 为中心。
- **exchange 信息交换层**:封装请求响应模式,同步转异步,以 `Request`, `Response` 为中心。
- **transport 网络传输层**:抽象 mina 和 netty 为统一接口,以 `Message` 为中心。
- **serialize 数据序列化层** :对需要在网络传输的数据进行序列化。
### Dubbo 的 SPI 机制了解么? 如何扩展 Dubbo 中的默认实现?
SPIService Provider Interface 机制被大量用在开源项目中,它可以帮助我们动态寻找服务/功能(比如负载均衡策略)的实现。
SPI 的具体原理是这样的:我们将接口的实现类放在配置文件中,我们在程序运行过程中读取配置文件,通过反射加载实现类。这样,我们可以在运行的时候,动态替换接口的实现类。和 IoC 的解耦思想是类似的。
Java 本身就提供了 SPI 机制的实现。不过Dubbo 没有直接用,而是对 Java原生的 SPI机制进行了增强以便更好满足自己的需求。
**那我们如何扩展 Dubbo 中的默认实现呢?**
比如说我们想要实现自己的负载均衡策略,我们创建对应的实现类 `XxxLoadBalance` 实现 `LoadBalance` 接口或者 `AbstractLoadBalance` 类。
```java
package com.xxx;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.RpcException;
public class XxxLoadBalance implements LoadBalance {
public <T> Invoker<T> select(List<Invoker<T>> invokers, Invocation invocation) throws RpcException {
// ...
}
}
```
我们将这个是实现类的路径写入到`resources` 目录下的 `META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance`文件中即可。
```java
src
|-main
|-java
|-com
|-xxx
|-XxxLoadBalance.java (实现LoadBalance接口)
|-resources
|-META-INF
|-dubbo
|-org.apache.dubbo.rpc.cluster.LoadBalance (纯文本文件内容为xxx=com.xxx.XxxLoadBalance)
```
`org.apache.dubbo.rpc.cluster.LoadBalance`
```
xxx=com.xxx.XxxLoadBalance
```
### Dubbo 的微内核架构了解吗?
Dubbo 采用 微内核Microkernel + 插件Plugin 模式,简单来说就是微内核架构。微内核只负责组装插件。
**何为微内核架构呢?** 《软件架构模式》 这本书是这样介绍的:
> 微内核架构模式(有时被称为插件架构模式)是实现基于产品应用程序的一种自然模式。基于产品的应用程序是已经打包好并且拥有不同版本,可作为第三方插件下载的。然后,很多公司也在开发、发布自己内部商业应用像有版本号、说明及可加载插件式的应用软件(这也是这种模式的特征)。微内核系统可让用户添加额外的应用如插件,到核心应用,继而提供了可扩展性和功能分离的用法。
微内核架构包含两类组件:**核心系统core system** 和 **插件模块plug-in modules**
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/source-code/dubbo/%E5%BE%AE%E5%86%85%E6%A0%B8%E6%9E%B6%E6%9E%84%E7%A4%BA%E6%84%8F%E5%9B%BE.png)
核心系统提供系统所需核心能力,插件模块可以扩展系统的功能。因此, 基于微内核架构的系统,非常易于扩展功能。
我们常见的一些IDE都可以看作是基于微内核架构设计的。绝大多数 IDE比如IDEA、VSCode都提供了插件来丰富自己的功能。
正是因为Dubbo基于微内核架构才使得我们可以随心所欲替换Dubbo的功能点。比如你觉得Dubbo 的序列化模块实现的不满足自己要求,没关系啊!你自己实现一个序列化模块就好了啊!
通常情况下,微核心都会采用 Factory、IoC、OSGi 等方式管理插件生命周期。Dubbo 不想依赖 Spring 等 IoC 容器,也不想自已造一个小的 IoC 容器(过度设计),因此采用了一种最简单的 Factory 方式管理插件 **JDK 标准的 SPI 扩展机制** `java.util.ServiceLoader`)。
### 关于Dubbo架构的一些自测小问题
#### 注册中心的作用了解么?
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互。
#### 服务提供者宕机后,注册中心会做什么?
注册中心会立即推送事件通知消费者。
#### 监控中心的作用呢?
监控中心负责统计各服务调用次数,调用时间等。
#### 注册中心和监控中心都宕机的话,服务都会挂掉吗?
不会。两者都宕机也不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。注册中心和监控中心都是可选的,服务消费者可以直连服务提供者。
## Dubbo 的负载均衡策略
### 什么是负载均衡?
先来看一下稍微官方点的解释。下面这段话摘自维基百科对负载均衡的定义:
### 2.2 Dubbo 工作原理
![Dubbo 工作原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/64702923.jpg)
图中从下至上分为十层各层均为单向依赖右边的黑色箭头代表层之间的依赖关系每一层都可以剥离上层被复用其中Service 和 Config 层为 API其它各层均为 SPI。
**各层说明**
- 第一层:**service层**,接口层,给服务提供者和消费者来实现的
- 第二层:**config层**配置层主要是对dubbo进行各种配置的
- 第三层:**proxy层**,服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton
- 第四层:**registry层**,服务注册层,负责服务的注册与发现
- 第五层:**cluster层**,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
- 第六层:**monitor层**监控层对rpc接口的调用次数和调用时间进行监控
- 第七层:**protocol层**远程调用层封装rpc调用
- 第八层:**exchange层**,信息交换层,封装请求响应模式,同步转异步
- 第九层:**transport层**网络传输层抽象mina和netty为统一接口
- 第十层:**serialize层**,数据序列化层,网络传输需要
## 三 Dubbo 的负载均衡策略
### 3.1 先来解释一下什么是负载均衡
**先来个官方的解释。**
> 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动)的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件。
> 负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动)的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件。
**上面讲的大家可能不太好理解,再用通俗的话给大家说一下。**
比如我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。
我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。
### 3.2 再来看看 Dubbo 提供的负载均衡策略
### Dubbo 提供的负载均衡策略有哪些?
在集群负载均衡时Dubbo 提供了多种均衡策略,默认为 `random` 随机调用。可以自行扩展负载均衡策略,参见:[负载均衡扩展](https://dubbo.gitbooks.io/dubbo-dev-book/content/impls/load-balance.html)。
在集群负载均衡时Dubbo 提供了多种均衡策略,默认为 `random` 随机调用。我们还可以自行扩展负载均衡策略参考Dubbo SPI机制
备注:下面的图片来自于尚硅谷2018Dubbo 视频。
#### 3.2.1 Random LoadBalance(默认,基于权重的随机负载均衡机制)
- **随机,按权重设置随机概率。**
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
![基于权重的随机负载均衡机制](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-7/77722327.jpg)
#### 3.2.2 RoundRobin LoadBalance(不推荐,基于权重的轮询负载均衡机制)
- 轮循,按公约后的权重设置轮循比率。
- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
![基于权重的轮询负载均衡机制](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-7/97933247.jpg)
#### 3.2.3 LeastActive LoadBalance
- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
#### 3.2.4 ConsistentHash LoadBalance
- **一致性 Hash相同参数的请求总是发到同一提供者。(如果你需要的不是随机负载均衡是要一类请求都到一个节点那就走这个一致性hash策略。)**
- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
- 算法参见http://en.wikipedia.org/wiki/Consistent_hashing
- 缺省只对第一个参数 Hash如果要修改请配置 `<dubbo:parameter key="hash.arguments" value="0,1" />`
- 缺省用 160 份虚拟节点,如果要修改,请配置 `<dubbo:parameter key="hash.nodes" value="320" />`
### 3.3 配置方式
**xml 配置方式**
服务端服务级别
在 Dubbo 中,所有负载均衡实现类均继承自 `AbstractLoadBalance`,该类实现了 `LoadBalance` 接口,并封装了一些公共的逻辑。
```java
<dubbo:service interface="..." loadbalance="roundrobin" />
public abstract class AbstractLoadBalance implements LoadBalance {
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
}
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
}
protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);
int getWeight(Invoker<?> invoker, Invocation invocation) {
}
}
```
客户端服务级别
`AbstractLoadBalance` 的实现类有下面这些:
![](/Users/guide/Library/Application Support/typora-user-images/image-20210326105257812.png)
官方文档对负载均衡这部分的介绍非常详细,推荐小伙伴们看看,地址:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance ) 。
#### RandomLoadBalance
根据权重随机选择对加权随机算法的实现。这是Dubbo默认采用的一种负载均衡策略。
` RandomLoadBalance` 具体的实现原理非常简单,假如有两个提供相同服务的服务器 S1,S2S1的权重为7S2的权重为3。
我们把这些权重值分布在坐标区间会得到S1->[0, 7) S2->(7, 10]。我们生成[0, 10) 之间的随机数,随机数落到对应的区间,我们就选择对应的服务器来处理请求。
![RandomLoadBalance](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/%20RandomLoadBalance.png)
`RandomLoadBalance` 的源码非常简单,简单花几分钟时间看一下。
> 以下源码来自 Dubbo master 分支上的最新的版本 2.7.9。
```java
<dubbo:reference interface="..." loadbalance="roundrobin" />
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = "random";
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size();
boolean sameWeight = true;
int[] weights = new int[length];
int totalWeight = 0;
// 下面这个for循环的主要作用就是计算所有该服务的提供者的权重之和 totalWeight
// 除此之外,还会检测每个服务提供者的权重是否相同
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
totalWeight += weight;
weights[i] = totalWeight;
if (sameWeight && totalWeight != weight * (i + 1)) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
// 随机生成一个 [0, totalWeight) 区间内的数字
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// 判断会落在哪个服务提供者的区间
for (int i = 0; i < length; i++) {
if (offset < weights[i]) {
return invokers.get(i);
}
}
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
```
服务端方法级别
#### LeastActiveLoadBalance
`LeastActiveLoadBalance` 直译过来就是**最小活跃数负载均衡**。
这个名字起得有点不直观,不仔细看官方对活跃数的定义,你压根不知道这玩意是干嘛的。
我这么说吧!初始状态下所有服务提供者的活跃数均为 0每个服务提供者的中特定方法都对应一个活跃数我在后面的源码中会提到每收到一个请求后对应的服务提供者的活跃数 +1当这个请求处理完之后活跃数 -1。
因此,**Dubbo 就认为谁的活跃数越少,谁的处理速度就越快,性能也越好,这样的话,我就优先把请求给活跃数少的服务提供者处理。**
**如果有多个服务提供者的活跃数相等怎么办?**
很简单,那就再走一遍 `RandomLoadBalance`
```java
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
public class LeastActiveLoadBalance extends AbstractLoadBalance {
public static final String NAME = "leastactive";
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size();
int leastActive = -1;
int leastCount = 0;
int[] leastIndexes = new int[length];
int[] weights = new int[length];
int totalWeight = 0;
int firstWeight = 0;
boolean sameWeight = true;
// 这个 for 循环的主要作用是遍历 invokers 列表,找出活跃数最小的 Invoker
// 如果有多个 Invoker 具有相同的最小活跃数,还会记录下这些 Invoker 在 invokers 集合中的下标,并累加它们的权重,比较它们的权重值是否相等
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
// 获取 invoker 对应的活跃(active)数
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
int afterWarmup = getWeight(invoker, invocation);
weights[i] = afterWarmup;
if (leastActive == -1 || active < leastActive) {
leastActive = active;
leastCount = 1;
leastIndexes[0] = i;
totalWeight = afterWarmup;
firstWeight = afterWarmup;
sameWeight = true;
} else if (active == leastActive) {
leastIndexes[leastCount++] = i;
totalWeight += afterWarmup;
if (sameWeight && afterWarmup != firstWeight) {
sameWeight = false;
}
}
}
// 如果只有一个 Invoker 具有最小的活跃数,此时直接返回该 Invoker 即可
if (leastCount == 1) {
return invokers.get(leastIndexes[0]);
}
// 如果有多个 Invoker 具有相同的最小活跃数,但它们之间的权重不同
// 这里的处理方式就和 RandomLoadBalance 一致了
if (!sameWeight && totalWeight > 0) {
int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexes[i];
offsetWeight -= weights[leastIndex];
if (offsetWeight < 0) {
return invokers.get(leastIndex);
}
}
}
return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}
}
```
客户端方法级别
活跃数是通过 `RpcStatus` 中的一个 `ConcurrentMap` 保存的,根据 URL 以及服务提供者被调用的方法的名称,我们便可以获取到对应的活跃数。也就是说服务提供者中的每一个方法的活跃数都是互相独立的。
```java
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
public class RpcStatus {
private static final ConcurrentMap<String, ConcurrentMap<String, RpcStatus>> METHOD_STATISTICS =
new ConcurrentHashMap<String, ConcurrentMap<String, RpcStatus>>();
public static RpcStatus getStatus(URL url, String methodName) {
String uri = url.toIdentityString();
ConcurrentMap<String, RpcStatus> map = METHOD_STATISTICS.computeIfAbsent(uri, k -> new ConcurrentHashMap<>());
return map.computeIfAbsent(methodName, k -> new RpcStatus());
}
public int getActive() {
return active.get();
}
}
```
**注解配置方式:**
#### ConsistentHashLoadBalance
消费方基于基于注解的服务级别配置方式:
`ConsistentHashLoadBalance` 小伙伴们应该也不会陌生,在分库分表、各种集群中就经常使用这个负载均衡策略。
```java
@Reference(loadbalance = "roundrobin")
HelloService helloService;
```
`ConsistentHashLoadBalance` 即**一致性Hash负载均衡策略**。 `ConsistentHashLoadBalance` 中没有权重的概念,具体是哪个服务提供者处理请求是由你的请求的参数决定的,也就是说相同参数的请求总是发到同一个服务提供者。
## 四 zookeeper宕机与dubbo直连的情况
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/consistent-hash-data-incline.jpg)
zookeeper宕机与dubbo直连的情况在面试中可能会被经常问到所以要引起重视。
在实际生产中假如zookeeper注册中心宕掉一段时间内服务消费方还是能够调用提供方的服务的实际上它使用的本地缓存进行通讯这只是dubbo健壮性的一种体现。
**dubbo的健壮性表现**
1. 监控中心宕掉不影响使用,只是丢失部分采样数据
2. 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
3. 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
4. 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
5. 服务提供者无状态,任意一台宕掉后,不影响使用
5. 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
我们前面提到过:注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。所以,我们可以完全可以绕过注册中心——采用 **dubbo 直连** ,即在服务消费方配置服务提供方的位置信息。
**xml配置方式**
```xml
<dubbo:reference id="userService" interface="com.zang.gmall.service.UserService" url="dubbo://localhost:20880" />
```
**注解方式:**
```java
@Reference(url = "127.0.0.1:20880")
HelloService helloService;
```
另外Dubbo 为了避免数据倾斜问题(节点不够分散,大量请求落到同一节点),还引入了虚拟节点的概念。通过虚拟节点可以让节点更加分散,有效均衡各个节点的请求量。
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/consistent-hash-invoker.jpg)
官方有详细的源码分析:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance) 。这里还有一个相关的 [PR#5440](https://github.com/apache/dubbo/pull/5440) 来修复老版本中 ConsistentHashLoadBalance 存在的一些Bug。感兴趣的小伙伴可以多花点时间研究一下。我这里不多分析了这个作业留给你们
#### RoundRobinLoadBalance
加权轮询负载均衡。
轮询就是把请求依次分配给每个服务提供者。加权轮询就是在轮询的基础上,让更多的请求落到权重更大的服务提供者上。比如假如有两个提供相同服务的服务器 S1,S2S1的权重为7S2的权重为3。
如果我们有 10 次请求,那么 7 次会被 S1处理3次被 S2处理。
但是,如果是 `RandomLoadBalance` 的话很可能存在10次请求有9次都被 S1 处理的情况(概率性问题)。
Dubbo 中的 `RoundRobinLoadBalance` 的代码实现被修改重建了好几次Dubbo-2.6.5 版本的 `RoundRobinLoadBalance` 为平滑加权轮询算法。
## Dubbo 序列化协议(代办)