Dubbo Cluster集群那點你不知道的事。


這是why技術的第33篇原創文章

本周是在家辦公的一周,上面的圖就是我在家的工位。

工欲善其事,必先利其器。在家辦公,我是認真的。

在家里開發的時候有需求是這樣的:一個如果接口調用失敗,需要自動進行重試。

雖然關系不大,但是我還是想到了Dubbo的集群容錯策略:Failover Cluster,即失敗自動切換。

(這個轉折是不是有點生硬.......)

所以借本文對於Dubbo的Cluster集群和Failover Cluster(失敗自動切換)策略進行一個詳細分析。

本文如果沒有特別說明的地方,源碼均是來自最新的2.7.5版本。

在閱讀之前先拋出幾個問題:

1.Dubbo Cluster集群的作用是什么?

2.Dubbo Cluster的10個實現類你能說出來幾個,其中哪幾個是集群容錯的方法實現?

3.默認的集群實現類是什么呢?

4.Failover Cluster調用失敗之后,會自動進行幾次重試呢?

5.什么是Dubbo的粘滯連接?

6.粘滯連接在Cluster中是怎么應用的?

7.Cluster選擇出一個可用的Invoker最多要進行幾次選擇?

8.請問幾次選擇分別是什么?

注意:上面的8個問題,前3個是非常常見的面試題。后面的都是你閱讀完本文后就可以知道問題的答案,面試中並不常見,但是后面的問題可以綜合成一個非常高頻的面試題:有看過什么源碼嗎,能給我講講嗎?

本文會對上面的問題進行逐一的、詳細的解讀。文章的最后會進行一個問題和答案的匯總。

廢話不多說,看完之后覺得不錯,還求個關注。抱拳了,老鐵。

Dubbo Cluster集群的作用是什么?

在生產環境,我們常常是多個服務器跑相同的應用,這種做的目的其一是為了避免單點故障。

為了避免單點故障,現在的應用通常至少會部署在兩台服務器上。而對於一些負載比較高的服務,比如網關服務,會部署更多的服務器。

這樣,在同一環境下的服務提供者數量會大於1。對於服務消費者來說,同一環境下出現了多個服務提供者。

這時會出現幾個問題:對於一次請求,我作為消費者到底調用哪個提供者呢?服務調用失敗的時候我怎么做呢?是重試?是拋出異常?或者僅僅是打印出異常?

為了處理這些問題,Dubbo定義了集群接口Cluster以及Cluster Invoker。

集群Cluster的用途是將多個服務提供者合並為一個Cluster Invoker,並將這個Invoker暴露給服務消費者。

這樣的好處就是對服務消費者來說,只需通過這個Cluster Invoker進行遠程調用即可,至於具體調用哪個服務提供者,以及調用失敗后如何處理等問題,現在都交給集群模塊去處理。

集群模塊是服務提供者和服務消費者的中間層,為服務消費者屏蔽了服務提供者的情況,這樣服務消費者就可以專心處理遠程調用相關事宜。比如發請求,接受服務提供者返回的數據等。這就是Dubbo Cluster集群的作用。

Dubbo Cluster的10個實現類是什么?

根據配置可以知道Dubbo集群接口Cluster有10種實現方法如下:

需要注意的是,十種實現方法其中只有failover、failfast、failsafe、failback、forking、broadcast這6種才屬於集群容錯的范疇。另外的實現均有其他的應用場景。

下面我們先說6種集群容錯的實現方法:

Failover Cluster:

failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster

失敗自動切換,在調用失敗時,失敗自動切換,當出現失敗,重試其它服務器。通常用於讀操作,但重試會帶來更長延遲。可通過retries="2"來設置重試次數(不含第一次)。

Failfast Cluster:

failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster

快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。

Failsafe Cluster:

failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster

失敗安全,出現異常時,直接忽略。通常用於寫入審計日志等操作。

Failback Cluster:

failback=org.apache.dubbo.rpc.cluster.support.FailbackCluster

失敗自動恢復,后台記錄失敗請求,定時重發。通常用於消息通知操作。

Forking Cluster:

forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster

並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2" 來設置最大並行數。

Broadcast Cluster:

broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster

廣播調用所有提供者,逐個調用,任意一台報錯則報錯。通常用於通知所有提供者更新緩存或日志等本地資源信息。

所以對於這個問題你也可以回答上來了:10個實現類中有哪幾個是集群容錯的方法實現?

接下來再說說另外四個實現類:

Available Cluster:

available=org.apache.dubbo.rpc.cluster.support.AvailableCluster

獲取可用的服務方。遍歷所有Invokers通過invoker.isAvalible判斷服務端是否活着,只要一個有為true,直接調用返回,不管成不成功。

Mergeable Cluster:

mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster

分組聚合,將集群中的調用結果聚合起來,然后再返回結果。比如菜單服務,接口一樣,但有多種實現,用group區分,現在消費方需從每種group中調用一次返回結果,合並結果返回,這樣就可以實現聚合菜單項。

Mock Cluster:

mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper

本地偽裝,通常用於服務降級,比如某驗權服務,當服務提供方全部掛掉后,客戶端不拋出異常,而是通過 Mock 數據返回授權失敗。

zone-aware Cluster:

zone-aware=org.apache.dubbo.rpc.cluster.support.registry.ZoneAwareCluster

上面的幾種Cluster策略在官網都能找到對應的說明,但是對於這個zone-aware目前官網上是沒有介紹的,因為這是前段時間發布的2.7.5版本才支持的內容,如下圖所示:

所以對於zone-aware這個策略我多說兩句。具體可以參照下面的這個issue: https://github.com/apache/dubbo/issues/5399

zone-aware的應用場景是下面這樣的。

業務部署假設是雙注冊中心:

則對應消費端,先在注冊中心間選擇,再到選定的注冊中心選址:

所以,和之前相比,在Dubbo 2.7.5以后,對於多注冊中心訂閱的場景,選址時的多了一層注冊中心集群間的負載均衡。

這個注冊中心集群間的負載均衡的實現就是:zone-aware Cluster。

對於多注冊中心間的選址策略,根據類上的注釋可以看出,目前設計的有下面四種:

1.指定優先級:

來自preferred="true"注冊中心的地址將被優先選擇,只有該中心無可用地址時才Fallback到其他注冊中心

<dubbo:registry address="zookeeper://${zookeeper.address1}" preferred="true" />

2.同 zone 優先:

選址時會和流量中的zone key做匹配,流量會優先派發到相同zone的地址

<dubbo:registry address="zookeeper://${zookeeper.address1}" zone="beijing" />

3.權重輪詢:

來自北京和上海集群的地址,將以10:1的比例來分配流量

<dubbo:registry id="beijing" address="zookeeper://${zookeeper.address1}" weight="100" />

<dubbo:registry id="shanghai" address="zookeeper://${zookeeper.address2}" weight="10" />

4.默認方法:

選擇第一個可用的即可。

默認的集群方法是什么呢?

源碼之下無秘密。我們從源碼中尋找答案:

首先我們可以看到,Cluster是一個SPI接口。其默認實現是FailoverCluster.NAME,如下源碼所示:

所以默認的實現方法就是:

org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker

由於Cluster是一個SPI接口,所以我們也可以根據實際需求去擴展自己的實現類。

FailoverCluster doInvoke源碼解析

接下來我們就對FailoverClusterInvoker的doInvoke方法的源碼進行解析。

這一小節主要回答這一個問題:Failover Cluster調用失敗之后,會自動切換Invoker進行幾次重試呢?

通過源碼,我們可以知道默認的重試次數是2次

有人就問了:為什么第61行的最后還有一個"+1"呢?

你想一想。我們想要在接口調用失敗后,重試n次,這個n就是DEFAULT_RETRIES,默認為2。那么我們總的調用次數就是n+1次了。所以這個"+1"是這樣來的,很小的一個點。

另外圖中標記了紅色五角星★的地方,第62到64行。也是很關鍵的地方。對於retries參數,在官網上的描述是這樣的:

不需要重試請設為0。我們前面分析了,當設置為0的時候,只會調用一次。

但是我也看見過retries配置為"-1"的。-1+1=0。調用0次明顯是一個錯誤的含義。但是程序也正常運行,且只調用一次。

這就是標記了紅色五角星★的地方的功勞了。防御性編程。哪怕你設置為-10086也只會調用一次。

接下來對doInvoke方法進行一個全面的解讀,下面是2.7.5版本的源碼,我基本上每一行主要的代碼都加了注釋,可以點開大圖查看: org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke

如上所示,FailoverClusterInvoker的doInvoke方法主要的工作流程是:

首先是獲取重試次數,然后根據重試次數進行循環調用,在循環體內,如果失敗,則進行重試。

在循環體內,首先是調用父類AbstractClusterInvoker的select方法,通過負載均衡組件選擇一個Invoker,然后再通過這個Invoker的invoke方法進行遠程調用。如果失敗了,記錄下異常,並進行重試。

注意一個細節:在進行重試前,重新獲取最新的invoker集合,這樣做的好處是,如果在重試的過程中某個服務掛了,可以通過調用list方法可以保證copyInvokers是最新的可用的invoker列表。

整個流程大致如此,不是很難理解。

什么是Dubbo的粘滯連接?

接下來我們要看的是父類AbstractClusterInvoker的select方法的邏輯。但是在看select方法的邏輯之前,我必須得先鋪墊一下Dubbo粘滯連接特性的知識。

官網上的解釋是這樣的:

可以看出,這是一個服務治理類型的參數。當設置true時,該接口上的所有方法使用同一個provider。官方文檔中說明可以用在接口和方法級別。

這些都是一些比較簡單的服務治理的規則。如果需求更復雜,則需要使用路由功能。

官方文檔已經說的很清楚了。我就只簡單的解釋一下第一句話:粘滯連接用於有狀態服務。

那么什么是有狀態服務,什么又是無狀態服務呢?

根據我簡單的理解。對服務提供者來說,究竟是有狀態的服務提供者,還是無狀態服務,其判斷依據就一句話: 從客戶端發起的兩個或者多個請求,在服務端是否具備上下文關系。

舉個例子,我們經常會用到的session。

眾所周知,HTTP協議是無狀態的。那么當在一個電商場景下,將用戶挑選的商品放到購物車,保存到session里,當付款的時候,再從購物車里取出商品信息。這樣通過session就實現了有狀態的服務。

當一個服務被設計為無狀態的時候,對於客戶端來說,可以隨意調用。所以無狀態的服務可以很容易的進行水平擴容。

當一個服務被設計為有狀態的時候,想要水平擴容的時候就不是那么簡單了。因為客戶端和服務端存在着上下文關系,所以客戶端每次都需要請求那一台服務端。

把一個有狀態的服務修改為無狀態的服務的方案也很簡單。還是拿session舉例,這個時候,我們的分布式session就呼之欲出了。把session集中存儲起來,比如放到redis中,弄一個獨立於服務的session共享層。這樣,一個有狀態的服務就可以變為一個無狀態的服務。

AbstractClusterInvoker select源碼解析

看完這一小節,你也就知道了粘滯連接在Cluster中是怎么應用的了。

有了粘滯連接的知識儲備后,再看select方法就比較輕松了,首先需要知道的是select方法是一個關鍵的公共方法,其作用就是選擇出一個可用的invoke,有下面這幾個Cluster的實現類都在調用:

代碼片段不長,邏輯也比較清楚,具體代碼解析如下:

根據代碼畫出select方法的流程圖如下:

結合代碼和流程圖,再進行一個文字描述。

先介紹一下select的四個入參,分別是:

loanbalance:負載均衡策略。

invocation:它持有調用過程中的變量,比如方法名,參數等。

invokers:這里的invokers列表可以看做是存活着的服務提供者列表。

selected:已經被選擇過的invoker集合。

通過源碼我們可以看出,select方法的主要邏輯集中在了對粘滯連接特性的支持上。

首先是獲取sticky配置,然后再檢測invokers列表中是否包含 stickyInvoker,如果不包含,則認為該stickyInvoker不可用,此時將其置空。

為什么可以置空?

因為這里的invokers列表是存活着的服務提供者列表,如果這個列表不包含stickyInvoker,那自然而然的認為stickyInvoker掛了,所以置空。

接下來,如果stickyInvoker存在於invokers列表中,說明stickyInvoker還活着,此時要進行下一項檢測。檢測selected(選擇過的服務提供者列表)中是否包含 stickyInvoker。

如果包含的話,說明stickyInvoker在此之前沒有成功提供服務(但其仍然處於存活狀態)。此時我們認為這個服務不可靠,不應該在重試期間內再次被調用,因此這個時候不會返回該stickyInvoker。

如果selected不包含stickyInvoker,此時還需要進行可用性檢測,比如檢測服務提供者網絡連通性等。當可用性檢測通過,才可返回 stickyInvoker,否則調用doSelect方法選擇Invoker。

如果sticky為true,此時會將doSelect方法選出的Invoker賦值給stickyInvoker。

關於粘滯連接和可用性檢測的默認配置如下:

即默認情況下粘滯連接是關閉狀態。當粘滯連接開啟時,默認會進行可用性檢查。

關於select方法先分析這么多,繼續向下分析。

AbstractClusterInvoker doSelect源碼解析

讀完這一小節你可以回答出這兩個問題:

1.Cluster選擇出一個可用的Invoker最多要進行幾次選擇?

2.請問幾次選擇分別是什么?

doSelect方法的源碼解析如下:

Failover Cluster選擇出一個可用的Invoker最多要進行三次選擇,也是doSelect的主要邏輯,三次分別是(圖中標號了①②③的地方):

①:通過負載均衡組件選擇 Invoker。

②:如果選出來的 Invoker 不穩定,或不可用,此時需要調用reselect 方法進行重選。

③:reselect選出來的Invoker為空,此時定位invoker在invokers列表中的位置index,然后獲取index+1處的 invoker。

AbstractClusterInvoker reselect源碼解析

下面我們來看一下 reselect 方法的邏輯。

根據源碼做出流程圖如下:

所以,reselect方法總結下來其實做了四件事情:

第一:查找可用的invoker,並將其添加到reselectInvokers集合中。這個reselectInvokers集合你可以理解為里面放的是所有的可用的invoker的集合與selected集合的差集。

第二:如果經過篩選后,reselectInvokers不為空,則通過負載均衡組件再次進行選擇並返回。

第三:如果經過篩選后,reselectInvokers為空,則再從selected集合(已經被調用過的集合)中選出所有可用的invoker,放到reselectInvokers中,再次通過負載均衡組件進行選擇並返回。

第四:如果進過上面的步驟后,沒有選擇出合適的invoker,reselectInvokers還是為空,說明所有的invoker都不可用了,返回為null。

好了,到這里就把最開始拋出的8個問題都解答完畢了,接下來對問題、答案進行一個匯總。

問題、答案匯總

1.Dubbo Cluster集群的作用是什么?

簡單來說:集群模塊是服務提供者和服務消費者的中間層,為服務消費者屏蔽了服務提供者的情況,這樣服務消費者就可以專心處理遠程調用相關事宜。比如發請求,接受服務提供者返回的數據等。這就是Dubbo Cluster集群的作用。

2.Dubbo Cluster的10個實現類你能說出來幾個,其中哪幾個是集群容錯的方法實現?

根據配置可以知道Dubbo集群接口Cluster有10種實現方法如下:

其中failover、failfast、failsafe、failback、forking、broadcast這6種才屬於集群容錯的范疇。另外的實現均有其他的應用場景。還需要注意的是zone-aware是2.7.5版本后才支持的實現類,之前是registryaware。

3.默認的集群實現類是什么呢?

失敗自動切換: org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker

4.Failover Cluster調用失敗之后,會自動切換Invoker進行幾次重試呢?

自動進行2次重試,共計調用3次。

5.什么是Dubbo的粘滯連接?

粘滯連接用於有狀態服務,盡可能讓客戶端總是向同一提供者發起調用,除非該提供者掛了,再連另一台。粘滯連接將自動開啟延遲連接,以減少長連接數。

6.粘滯連接在Cluster中是怎么應用的?

參照AbstractClusterInvoker select源碼解析。select方法的主要邏輯集中在了對粘滯連接特性的支持上。

7.Cluster選擇出一個可用的Invoker最多要進行幾次選擇?

最多進行三次選擇。

8.請問幾次選擇分別是什么?

①:通過負載均衡組件選擇 Invoker。 ②:如果選出來的 Invoker 不穩定,或不可用,此時需要調用reselect 方法進行重選。 ③:reselect選出來的Invoker為空,此時定位invoker在invokers列表中的位置index,然后獲取index+1處的 invoker。

最后說一句(求個關注)

之前也寫過幾篇Dubbo相關的文章,有興趣的可以看一看:

《Dubbo 2.7.5在線程模型上的優化》

《快速失敗機制&失敗安全機制》

《夠強!一行代碼就修復了我提的Dubbo的Bug。》

《Dubbo加權輪詢負載均衡的源碼和Bug,了解一下?》

《Dubbo一致性哈希負載均衡的源碼和Bug,了解一下?》

《一文講透Dubbo負載均衡之最小活躍數算法》

《參加Dubbo社區開發者日成都站后,帶給我的一點思考。》

《Dubbo 2.7新特性之異步化改造》

才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。

感謝您的閱讀,原創不易,求個關注.

以上。

歡迎關注公眾號【why技術】,堅持輸出原創。願你我共同進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM