Dubbo 面試題,淦!!!
大家好,今天是分享 Dubbo 知識(shí)點(diǎn)/面試題總結(jié)的 Guide!
這篇文章是我根據(jù)官方文檔以及自己平時(shí)的使用情況,對 Dubbo 所做的一個(gè)總結(jié)。
文章內(nèi)容有一點(diǎn)長,一次看不完的話,記得收藏起來,找時(shí)間再看!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Dubbo 支持多種序列化方式:JDK 自帶的序列化、hessian2、JSON、Kryo、FST、Protostuff,ProtoBuf 等等。
Dubbo 默認(rèn)使用的序列化方式是 hession2。
談?wù)勀銓@些序列化協(xié)議了解?
一般我們不會(huì)直接使用 JDK 自帶的序列化方式。主要原因有兩個(gè):
不支持跨語言調(diào)用 : 如果調(diào)用的是其他語言開發(fā)的服務(wù)的時(shí)候就不支持了。 性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字節(jié)數(shù)組體積較大,導(dǎo)致傳輸成本加大。
JSON 序列化由于性能問題,我們一般也不會(huì)考慮使用。
像 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 官方文檔中還有一個(gè)關(guān)于這些序列化協(xié)議的性能對比圖可供參考。

往期精選
我真不想學(xué) happens - before 了!
2W字!梳理50道經(jīng)典計(jì)算機(jī)網(wǎng)絡(luò)面試題

