1w+字的 Dubbo 面試題/知識點總結?。?021 最新版)

這篇文章是我根據(jù)官方文檔以及自己平時的使用情況,對 Dubbo 所做的一個總結。
文章內容有一點長,一次看不完的話,記得收藏起來,找時間再看!

原創(chuàng)不易,若有幫助,點贊/分享就是對我最大的鼓勵!
個人能力有限。如果文章有任何需要補充/完善/修改的地方,歡迎在評論區(qū)指出,共同進步!
RPC 基礎
何為 RPC?
RPC(Remote Procedure Call) 即遠程過程調用,通過名字我們就能看出 RPC 關注的是遠程調用而非本地調用。
為什么要 RPC ? 因為,兩個不同的服務器上的服務提供的方法不在一個內存空間,所以,需要通過網(wǎng)絡編程才能傳遞方法調用所需要的參數(shù)。并且,方法調用的結果也需要通過網(wǎng)絡編程來接收。但是,如果我們自己手動網(wǎng)絡編程來實現(xiàn)這個調用過程的話工作量是非常大的,因為,我們需要考慮底層傳輸方式(TCP 還是 UDP)、序列化方式等等方面。
RPC 能幫助我們做什么呢? 簡單來說,通過 RPC 可以幫助我們調用遠程計算機上某個服務的方法,這個過程就像調用本地方法一樣簡單。并且!我們不需要了解底層網(wǎng)絡編程的具體細節(jié)。
舉個例子:兩個不同的服務 A、B 部署在兩臺不同的機器上,服務 A 如果想要調用服務 B 中的某個方法的話就可以通過 RPC 來做。
一言蔽之:RPC 的出現(xiàn)就是為了讓你調用遠程方法像調用本地方法一樣簡單。
RPC 的原理是什么?
為了能夠幫助小伙伴們理解 RPC 原理,我們可以將整個 RPC 的 核心功能看作是下面 ?? 6 個部分實現(xiàn)的:
客戶端(服務消費端) :調用遠程方法的一端。 客戶端 Stub(樁) :這其實就是一代理類。代理類主要做的事情很簡單,就是把你調用方法、類、方法參數(shù)等信息傳遞到服務端。 網(wǎng)絡傳輸 :網(wǎng)絡傳輸就是你要把你調用的方法的信息比如說參數(shù)啊這些東西傳輸?shù)椒斩?,然后服務端?zhí)行完之后再把返回結果通過網(wǎng)絡傳輸給你傳輸回來。網(wǎng)絡傳輸?shù)膶崿F(xiàn)方式有很多種比如最近基本的 Socket 或者性能以及封裝更加優(yōu)秀的 Netty(推薦)。 服務端 Stub(樁) :這個樁就不是代理類了。我覺得理解為樁實際不太好,大家注意一下就好。這里的服務端 Stub 實際指的就是接收到客戶端執(zhí)行方法的請求后,去指定對應的方法然后返回結果給客戶端的類。 服務端(服務提供端) :提供遠程方法的一端。
具體原理圖如下,后面我會串起來將整個 RPC 的過程給大家說一下。

服務消費端(client)以本地調用的方式調用遠程服務; 客戶端 Stub(client stub) 接收到調用后負責將方法、參數(shù)等組裝成能夠進行網(wǎng)絡傳輸?shù)南Ⅲw(序列化): RpcRequest;客戶端 Stub(client stub) 找到遠程服務的地址,并將消息發(fā)送到服務提供端; 服務端 Stub(樁)收到消息將消息反序列化為 Java 對象: RpcRequest;服務端 Stub(樁)根據(jù) RpcRequest中的類、方法、方法參數(shù)等信息調用本地的方法;服務端 Stub(樁)得到方法執(zhí)行結果并將組裝成能夠進行網(wǎng)絡傳輸?shù)南Ⅲw: RpcResponse(序列化)發(fā)送至消費方;客戶端 Stub(client stub)接收到消息并將消息反序列化為 Java 對象: RpcResponse,這樣也就得到了最終結果。over!
相信小伙伴們看完上面的講解之后,已經(jīng)了解了 RPC 的原理。
雖然篇幅不多,但是基本把 RPC 框架的核心原理講清楚了!另外,對于上面的技術細節(jié),我會在后面的章節(jié)介紹到。
最后,對于 RPC 的原理,希望小伙伴不單單要理解,還要能夠自己畫出來并且能夠給別人講出來。因為,在面試中這個問題在面試官問到 RPC 相關內容的時候基本都會碰到。
Dubbo 基礎
什么是 Dubbo?

Apache Dubbo (incubating) |?d?b??| 是一款高性能、輕量級的開源 Java RPC 框架。
根據(jù) Dubbo 官方文檔的介紹,Dubbo 提供了六大核心能力
面向接口代理的高性能 RPC 調用。 智能容錯和負載均衡。 服務自動注冊和發(fā)現(xiàn)。 高度可擴展能力。 運行期流量調度。 可視化的服務治理與運維。

簡單來說就是:Dubbo 不光可以幫助我們調用遠程服務,還提供了一些其他開箱即用的功能比如智能負載均衡。
Dubbo 目前已經(jīng)有接近 34.4 k 的 Star 。
在 2020 年度 OSC 中國開源項目 評選活動中,Dubbo 位列開發(fā)框架和基礎組件類項目的第 7 名。想比幾年前來說,熱度和排名有所下降。

Dubbo 是由阿里開源,后來加入了 Apache 。正式由于 Dubbo 的出現(xiàn),才使得越來越多的公司開始使用以及接受分布式架構。
為什么要用 Dubbo?
隨著互聯(lián)網(wǎng)的發(fā)展,網(wǎng)站的規(guī)模越來越大,用戶數(shù)量越來越多。單一應用架構 、垂直應用架構無法滿足我們的需求,這個時候分布式服務架構就誕生了。
分布式服務架構下,系統(tǒng)被拆分成不同的服務比如短信服務、安全服務,每個服務獨立提供系統(tǒng)的某個核心服務。
我們可以使用 Java RMI(Java Remote Method Invocation)、Hessian 這種支持遠程調用的框架來簡單地暴露和引用遠程服務。但是!當服務越來越多之后,服務調用關系越來越復雜。當應用訪問壓力越來越大后,負載均衡以及服務監(jiān)控的需求也迫在眉睫。我們可以用 F5 這類硬件來做負載均衡,但這樣增加了成本,并且存在單點故障的風險。
不過,Dubbo 的出現(xiàn)讓上述問題得到了解決。Dubbo 幫助我們解決了什么問題呢?
負載均衡 :同一個服務部署在不同的機器時該調用那一臺機器上的服務。 服務調用鏈路生成 :隨著系統(tǒng)的發(fā)展,服務越來越多,服務間依賴關系變得錯蹤復雜,甚至分不清哪個應用要在哪個應用之前啟動,架構師都不能完整的描述應用的架構關系。Dubbo 可以為我們解決服務之間互相是如何調用的。 服務訪問壓力以及時長統(tǒng)計、資源調度和治理 :基于訪問壓力實時管理集群容量,提高集群利用率。 ......

另外,Dubbo 除了能夠應用在分布式系統(tǒng)中,也可以應用在現(xiàn)在比較火的微服務系統(tǒng)中。不過,由于 Spring Cloud 在微服務中應用更加廣泛,所以,我覺得一般我們提 Dubbo 的話,大部分是分布式系統(tǒng)的情況。
我們剛剛提到了分布式這個概念,下面再給大家介紹一下什么是分布式?為什么要分布式?
分布式基礎
什么是分布式?
分布式或者說 SOA 分布式重要的就是面向服務,說簡單的分布式就是我們把整個系統(tǒng)拆分成不同的服務然后將這些服務放在不同的服務器上減輕單體服務的壓力提高并發(fā)量和性能。比如電商系統(tǒng)可以簡單地拆分成訂單系統(tǒng)、商品系統(tǒng)、登錄系統(tǒng)等等,拆分之后的每個服務可以部署在不同的機器上,如果某一個服務的訪問量比較大的話也可以將這個服務同時部署在多臺機器上。

為什么要分布式?
從開發(fā)角度來講單體應用的代碼都集中在一起,而分布式系統(tǒng)的代碼根據(jù)業(yè)務被拆分。所以,每個團隊可以負責一個服務的開發(fā),這樣提升了開發(fā)效率。另外,代碼根據(jù)業(yè)務拆分之后更加便于維護和擴展。
另外,我覺得將系統(tǒng)拆分成分布式之后不光便于系統(tǒng)擴展和維護,更能提高整個系統(tǒng)的性能。你想一想嘛?把整個系統(tǒng)拆分成不同的服務/系統(tǒng),然后每個服務/系統(tǒng) 單獨部署在一臺服務器上,是不是很大程度上提高了系統(tǒng)性能呢?
Dubbo 架構
Dubbo 架構中的核心角色有哪些?
官方文檔中的框架設計章節(jié) 已經(jīng)介紹的非常詳細了,我這里把一些比較重要的點再提一下。

上述節(jié)點簡單介紹以及他們之間的關系:
Container: 服務運行容器,負責加載、運行服務提供者。必須。 Provider: 暴露服務的服務提供方,會向注冊中心注冊自己提供的服務。必須。 Consumer: 調用遠程服務的服務消費方,會向注冊中心訂閱自己所需的服務。必須。 Registry: 服務注冊與發(fā)現(xiàn)的注冊中心。注冊中心會返回服務提供者地址列表給消費者。非必須。 Monitor: 統(tǒng)計服務的調用次數(shù)和調用時間的監(jiān)控中心。服務消費者和提供者會定時發(fā)送統(tǒng)計數(shù)據(jù)到監(jiān)控中心。非必須。
Dubbo 中的 Invoker 概念了解么?
Invoker 是 Dubbo 領域模型中非常重要的一個概念,你如果閱讀過 Dubbo 源碼的話,你會無數(shù)次看到這玩意。就比如下面我要說的負載均衡這塊的源碼中就有大量 Invoker 的身影。
簡單來說,Invoker 就是 Dubbo 對遠程調用的抽象。

按照 Dubbo 官方的話來說,Invoker 分為
服務提供 Invoker服務消費 Invoker
假如我們需要調用一個遠程方法,我們需要動態(tài)代理來屏蔽遠程調用的細節(jié)吧!我們屏蔽掉的這些細節(jié)就依賴對應的 Invoker 實現(xiàn), Invoker 實現(xiàn)了真正的遠程服務調用。
Dubbo 的工作原理了解么?
下圖是 Dubbo 的整體設計,從下至上分為十層,各層均為單向依賴。
左邊淡藍背景的為服務消費方使用的接口,右邊淡綠色背景的為服務提供方使用的接口,位于中軸線上的為雙方都用到的接口。

config 配置層:Dubbo 相關的配置。支持代碼配置,同時也支持基于 Spring 來做配置,以 ServiceConfig,ReferenceConfig為中心proxy 服務代理層:調用遠程方法像調用本地的方法一樣簡單的一個關鍵,真實調用過程依賴代理類,以 ServiceProxy為中心。registry 注冊中心層:封裝服務地址的注冊與發(fā)現(xiàn)。 cluster 路由層:封裝多個提供者的路由及負載均衡,并橋接注冊中心,以 Invoker為中心。monitor 監(jiān)控層:RPC 調用次數(shù)和調用時間監(jiān)控,以 Statistics為中心。protocol 遠程調用層:封裝 RPC 調用,以 Invocation,Result為中心。exchange 信息交換層:封裝請求響應模式,同步轉異步,以 Request,Response為中心。transport 網(wǎng)絡傳輸層:抽象 mina 和 netty 為統(tǒng)一接口,以 Message為中心。serialize 數(shù)據(jù)序列化層 :對需要在網(wǎng)絡傳輸?shù)臄?shù)據(jù)進行序列化。
Dubbo 的 SPI 機制了解么?如何擴展 Dubbo 中的默認實現(xiàn)?
SPI(Service Provider Interface) 機制被大量用在開源項目中,它可以幫助我們動態(tài)尋找服務/功能(比如負載均衡策略)的實現(xiàn)。
SPI 的具體原理是這樣的:我們將接口的實現(xiàn)類放在配置文件中,我們在程序運行過程中讀取配置文件,通過反射加載實現(xiàn)類。這樣,我們可以在運行的時候,動態(tài)替換接口的實現(xiàn)類。和 IoC 的解耦思想是類似的。
Java 本身就提供了 SPI 機制的實現(xiàn)。不過,Dubbo 沒有直接用,而是對 Java 原生的 SPI 機制進行了增強,以便更好滿足自己的需求。
那我們如何擴展 Dubbo 中的默認實現(xiàn)呢?
比如說我們想要實現(xiàn)自己的負載均衡策略,我們創(chuàng)建對應的實現(xiàn)類 XxxLoadBalance 實現(xiàn) LoadBalance 接口或者 AbstractLoadBalance 類。
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 {
// ...
}
}
我們將這個是實現(xiàn)類的路徑寫入到resources 目錄下的 META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance文件中即可。
src
|-main
|-java
|-com
|-xxx
|-XxxLoadBalance.java (實現(xiàn)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
其他還有很多可供擴展的選擇,你可以在官方文檔@SPI 擴展實現(xiàn)這里找到。

Dubbo 的微內核架構了解嗎?
Dubbo 采用 微內核(Microkernel) + 插件(Plugin) 模式,簡單來說就是微內核架構。微內核只負責組裝插件。
何為微內核架構呢? 《軟件架構模式》 這本書是這樣介紹的:
微內核架構模式(有時被稱為插件架構模式)是實現(xiàn)基于產(chǎn)品應用程序的一種自然模式?;诋a(chǎn)品的應用程序是已經(jīng)打包好并且擁有不同版本,可作為第三方插件下載的。然后,很多公司也在開發(fā)、發(fā)布自己內部商業(yè)應用像有版本號、說明及可加載插件式的應用軟件(這也是這種模式的特征)。微內核系統(tǒng)可讓用戶添加額外的應用如插件,到核心應用,繼而提供了可擴展性和功能分離的用法。
微內核架構包含兩類組件:核心系統(tǒng)(core system) 和 插件模塊(plug-in modules)。

核心系統(tǒng)提供系統(tǒng)所需核心能力,插件模塊可以擴展系統(tǒng)的功能。因此, 基于微內核架構的系統(tǒng),非常易于擴展功能。
我們常見的一些 IDE,都可以看作是基于微內核架構設計的。絕大多數(shù) IDE 比如 IDEA、VSCode 都提供了插件來豐富自己的功能。
正是因為 Dubbo 基于微內核架構,才使得我們可以隨心所欲替換 Dubbo 的功能點。比如你覺得 Dubbo 的序列化模塊實現(xiàn)的不滿足自己要求,沒關系??!你自己實現(xiàn)一個序列化模塊就好了??!
通常情況下,微核心都會采用 Factory、IoC、OSGi 等方式管理插件生命周期。Dubbo 不想依賴 Spring 等 IoC 容器,也不想自已造一個小的 IoC 容器(過度設計),因此采用了一種最簡單的 Factory 方式管理插件 :JDK 標準的 SPI 擴展機制 (java.util.ServiceLoader)。
關于 Dubbo 架構的一些自測小問題
注冊中心的作用了解么?
注冊中心負責服務地址的注冊與查找,相當于目錄服務,服務提供者和消費者只在啟動時與注冊中心交互。
服務提供者宕機后,注冊中心會做什么?
注冊中心會立即推送事件通知消費者。
監(jiān)控中心的作用呢?
監(jiān)控中心負責統(tǒng)計各服務調用次數(shù),調用時間等。
注冊中心和監(jiān)控中心都宕機的話,服務都會掛掉嗎?
不會。兩者都宕機也不影響已運行的提供者和消費者,消費者在本地緩存了提供者列表。注冊中心和監(jiān)控中心都是可選的,服務消費者可以直連服務提供者。
Dubbo 的負載均衡策略
什么是負載均衡?
先來看一下稍微官方點的解釋。下面這段話摘自維基百科對負載均衡的定義:
負載均衡改善了跨多個計算資源(例如計算機,計算機集群,網(wǎng)絡鏈接,中央處理單元或磁盤驅動)的工作負載分布。負載平衡旨在優(yōu)化資源使用,最大化吞吐量,最小化響應時間,并避免任何單個資源的過載。使用具有負載平衡而不是單個組件的多個組件可以通過冗余提高可靠性和可用性。負載平衡通常涉及專用軟件或硬件。
上面講的大家可能不太好理解,再用通俗的話給大家說一下。
我們的系統(tǒng)中的某個服務的訪問量特別大,我們將這個服務部署在了多臺服務器上,當客戶端發(fā)起請求的時候,多臺服務器都可以處理這個請求。那么,如何正確選擇處理該請求的服務器就很關鍵。假如,你就要一臺服務器來處理該服務的請求,那該服務部署在多臺服務器的意義就不復存在了。負載均衡就是為了避免單個服務器響應同一請求,容易造成服務器宕機、崩潰等問題,我們從負載均衡的這四個字就能明顯感受到它的意義。
Dubbo 提供的負載均衡策略有哪些?
在集群負載均衡時,Dubbo 提供了多種均衡策略,默認為 random 隨機調用。我們還可以自行擴展負載均衡策略(參考 Dubbo SPI 機制)。
在 Dubbo 中,所有負載均衡實現(xiàn)類均繼承自 AbstractLoadBalance,該類實現(xiàn)了 LoadBalance 接口,并封裝了一些公共的邏輯。
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 的實現(xiàn)類有下面這些:

官方文檔對負載均衡這部分的介紹非常詳細,推薦小伙伴們看看,地址:https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance 。
RandomLoadBalance
根據(jù)權重隨機選擇(對加權隨機算法的實現(xiàn))。這是 Dubbo 默認采用的一種負載均衡策略。
RandomLoadBalance 具體的實現(xiàn)原理非常簡單,假如有兩個提供相同服務的服務器 S1,S2,S1 的權重為 7,S2 的權重為 3。
我們把這些權重值分布在坐標區(qū)間會得到:S1->[0, 7) ,S2->(7, 10]。我們生成[0, 10) 之間的隨機數(shù),隨機數(shù)落到對應的區(qū)間,我們就選擇對應的服務器來處理請求。

RandomLoadBalance 的源碼非常簡單,簡單花幾分鐘時間看一下。
以下源碼來自 Dubbo master 分支上的最新的版本 2.7.9。
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循環(huán)的主要作用就是計算所有該服務的提供者的權重之和 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) 區(qū)間內的數(shù)字
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// 判斷會落在哪個服務提供者的區(qū)間
for (int i = 0; i < length; i++) {
if (offset < weights[i]) {
return invokers.get(i);
}
}
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
LeastActiveLoadBalance
LeastActiveLoadBalance 直譯過來就是最小活躍數(shù)負載均衡。
這個名字起得有點不直觀,不仔細看官方對活躍數(shù)的定義,你壓根不知道這玩意是干嘛的。
我這么說吧!初始狀態(tài)下所有服務提供者的活躍數(shù)均為 0(每個服務提供者的中特定方法都對應一個活躍數(shù),我在后面的源碼中會提到),每收到一個請求后,對應的服務提供者的活躍數(shù) +1,當這個請求處理完之后,活躍數(shù) -1。
因此,Dubbo 就認為誰的活躍數(shù)越少,誰的處理速度就越快,性能也越好,這樣的話,我就優(yōu)先把請求給活躍數(shù)少的服務提供者處理。
如果有多個服務提供者的活躍數(shù)相等怎么辦?
很簡單,那就再走一遍 RandomLoadBalance 。
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 循環(huán)的主要作用是遍歷 invokers 列表,找出活躍數(shù)最小的 Invoker
// 如果有多個 Invoker 具有相同的最小活躍數(shù),還會記錄下這些 Invoker 在 invokers 集合中的下標,并累加它們的權重,比較它們的權重值是否相等
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
// 獲取 invoker 對應的活躍(active)數(shù)
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 具有最小的活躍數(shù),此時直接返回該 Invoker 即可
if (leastCount == 1) {
return invokers.get(leastIndexes[0]);
}
// 如果有多個 Invoker 具有相同的最小活躍數(shù),但它們之間的權重不同
// 這里的處理方式就和 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)]);
}
}
活躍數(shù)是通過 RpcStatus 中的一個 ConcurrentMap 保存的,根據(jù) URL 以及服務提供者被調用的方法的名稱,我們便可以獲取到對應的活躍數(shù)。也就是說服務提供者中的每一個方法的活躍數(shù)都是互相獨立的。
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 小伙伴們應該也不會陌生,在分庫分表、各種集群中就經(jīng)常使用這個負載均衡策略。
ConsistentHashLoadBalance 即一致性 Hash 負載均衡策略。ConsistentHashLoadBalance 中沒有權重的概念,具體是哪個服務提供者處理請求是由你的請求的參數(shù)決定的,也就是說相同參數(shù)的請求總是發(fā)到同一個服務提供者。

另外,Dubbo 為了避免數(shù)據(jù)傾斜問題(節(jié)點不夠分散,大量請求落到同一節(jié)點),還引入了虛擬節(jié)點的概念。通過虛擬節(jié)點可以讓節(jié)點更加分散,有效均衡各個節(jié)點的請求量。

官方有詳細的源碼分析:https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance 。這里還有一個相關的 PR#5440 來修復老版本中 ConsistentHashLoadBalance 存在的一些 Bug。感興趣的小伙伴,可以多花點時間研究一下。我這里不多分析了,這個作業(yè)留給你們!
RoundRobinLoadBalance
加權輪詢負載均衡。
輪詢就是把請求依次分配給每個服務提供者。加權輪詢就是在輪詢的基礎上,讓更多的請求落到權重更大的服務提供者上。比如假如有兩個提供相同服務的服務器 S1,S2,S1 的權重為 7,S2 的權重為 3。
如果我們有 10 次請求,那么 7 次會被 S1 處理,3 次被 S2 處理。
但是,如果是 RandomLoadBalance 的話,很可能存在 10 次請求有 9 次都被 S1 處理的情況(概率性問題)。
Dubbo 中的 RoundRobinLoadBalance 的代碼實現(xiàn)被修改重建了好幾次,Dubbo-2.6.5 版本的 RoundRobinLoadBalance 為平滑加權輪詢算法。
Dubbo 序列化協(xié)議
Dubbo 支持哪些序列化方式呢?

Dubbo 支持多種序列化方式:JDK 自帶的序列化、hessian2、JSON、Kryo、FST、Protostuff,ProtoBuf 等等。
Dubbo 默認使用的序列化方式是 hession2。
談談你對這些序列化協(xié)議了解?
一般我們不會直接使用 JDK 自帶的序列化方式。主要原因有兩個:
不支持跨語言調用 : 如果調用的是其他語言開發(fā)的服務的時候就不支持了。 性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字節(jié)數(shù)組體積較大,導致傳輸成本加大。
JSON 序列化由于性能問題,我們一般也不會考慮使用。
像 Protostuff,ProtoBuf、hessian2 這些都是跨語言的序列化方式,如果有跨語言需求的話可以考慮使用。
Kryo 和 FST 這兩種序列化方式是 Dubbo 后來才引入的,性能非常好。不過,這兩者都是專門針對 Java 語言的。Dubbo 官網(wǎng)的一篇文章中提到說推薦使用 Kryo 作為生產(chǎn)環(huán)境的序列化方式。(文章地址:https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/)

Dubbo 官方文檔中還有一個關于這些序列化協(xié)議的性能對比圖可供參考。

