天天看點

Dubbo 源碼分析 - 叢集容錯之 Cluster

為了避免單點故障,現在的應用至少會部署在兩台伺服器上。對于一些負載比較高的服務,會部署更多台伺服器。這樣,同一環境下的服務提供者數量會大于1。對于服務消費者來說,同一環境下出現了多個服務提供者。這時會出現一個問題,服務消費者需要決定選擇哪個服務提供者進行調用。另外服務調用失敗時的處理措施也是需要考慮的,是重試呢,還是抛出異常,亦或是隻列印異常等。為了處理這些問題,Dubbo 定義了叢集接口 Cluster 以及及 Cluster Invoker。叢集 Cluster 用途是将多個服務提供者合并為一個 Cluster Invoker,并将這個 Invoker 暴露給服務消費者。這樣一來,服務消費者隻需通過這個 Invoker 進行遠端調用即可,至于具體調用哪個服務提供者,以及調用失敗後如何處理等問題,現在都交給叢集子產品去處理。叢集子產品是服務提供者和服務消費者的中間層,為服務消費者屏蔽了服務提供者的情況,這樣服務消費者就可以處理遠端調用相關事宜。比如發請求,接受服務提供者傳回的資料等。這就是叢集的作用。

Dubbo 源碼分析 - 叢集容錯之 Cluster

Dubbo 提供了多種叢集實作,包含但不限于 Failover Cluster、Failfast Cluster 和 Failsafe Cluster 等。每種叢集實作類的用途不同,接下來我會一一進行分析。

在對叢集相關代碼進行分析之前,這裡有必要先來介紹一下叢集容錯的所有元件。包含 Cluster、Cluster Invoker、Directory、Router 和 LoadBalance 等,先來看圖。

Dubbo 源碼分析 - 叢集容錯之 Cluster

* 圖檔來源:Dubbo 官方文檔

這張圖來自 Dubbo 官方文檔,接下來我會按照這張圖介紹叢集工作過程。叢集工作過程可分為兩個階段,第一個階段是在服務消費者初始化期間,叢集 Cluster 實作類為服務消費者建立 Cluster Invoker 執行個體,即上圖中的 merge 操作。第二個階段是在服務消費者進行遠端調用時。以 FailoverClusterInvoker 為例,該類型 Cluster Invoker 首先會調用 Directory 的 list 方法列舉 Invoker 清單(可将 Invoker 簡單了解為服務提供者)。Directory 的用途是儲存 Invoker,可簡單類比為 List<Invoker>。其實作類 RegistryDirectory 是一個動态服務目錄,可感覺注冊中心配置的變化,它所持有的 Inovker 清單會随着注冊中心内容的變化而變化。每次變化後,RegistryDirectory 會動态增删 Inovker,并調用 Router 的 route 方法進行路由,過濾掉不符合路由規則的 Invoker。回到上圖,Cluster Invoker 實際上并不會直接調用 Router 進行路由。當 FailoverClusterInvoker 拿到 Directory 傳回的 Invoker 清單後,它會通過 LoadBalance 從 Invoker 清單中選擇一個 Inovker。最後 FailoverClusterInvoker 會将參數傳給 LoadBalance 選擇出的 Invoker 執行個體的 invoker 方法,進行真正的 RPC 調用。

以上就是叢集工作的整個流程,這裡并沒介紹叢集是如何容錯的。Dubbo 主要提供了這樣幾種容錯方式:

Failover Cluster - 失敗自動切換

Failfast Cluster - 快速失敗

Failsafe Cluster - 失敗安全

Failback Cluster - 失敗自動恢複

Forking Cluster - 并行調用多個服務提供者

這裡暫時隻對這幾種容錯模式進行簡單的介紹,在接下來的章節中,我會重點分析這幾種容錯模式的具體實作。好了,關于叢集的工作流程和容錯模式先說到這,接下來進入源碼分析階段。

我在上一章提到了叢集接口 Cluster 和 Cluster Invoker,這兩者是不同的。Cluster 是接口,而 Cluster Invoker 是一種 Invoker。服務提供者的選擇邏輯,以及遠端調用失敗後的的處理邏輯均是封裝在 Cluster Invoker 中。那麼 Cluster 接口和相關實作類有什麼用呢?用途比較簡單,用于生成 Cluster Invoker,僅此而已。下面我們來看一下源碼。

如上,FailoverCluster 總共就包含這幾行代碼,用于建立 FailoverClusterInvoker 對象,很簡單。下面再看一個。

如上,FailbackCluster 的邏輯也是很簡單,無需解釋了。是以接下來,我們把重點放在各種 Cluster Invoker 上

我們首先從各種 Cluster Invoker 的父類 AbstractClusterInvoker 源碼開始說起。前面說過,叢集工作過程可分為兩個階段,第一個階段是在服務消費者初始化期間,這個在服務引用那篇文章中已經分析過了,這裡不再贅述。第二個階段是在服務消費者進行遠端調用時,此時 AbstractClusterInvoker 的 invoke 方法會被調用。列舉 Invoker,負載均衡等操作均會在此階段被執行。是以下面先來看一下 invoke 方法的邏輯。

AbstractClusterInvoker 的 invoke 方法主要用于列舉 Invoker,以及加載 LoadBalance。最後再調用模闆方法 doInvoke 進行後續操作。下面我們來看一下 Invoker 列舉方法 list(Invocation) 的邏輯,如下:

如上,AbstractClusterInvoker 中的 list 方法做的事情很簡單,隻是簡單的調用了 Directory 的 list 方法,沒有其他更多的邏輯了。Directory 的 list 方法我在前面的文章中已經分析過了,這裡就不贅述了。

接下來,我們把目光轉移到 AbstractClusterInvoker 的各種實作類上,來看一下這些實作類是如何實作 doInvoke 方法邏輯的。

FailoverClusterInvoker 在調用失敗時,會自動切換 Invoker 進行重試。在無明确配置下,Dubbo 會使用這個類作為預設 Cluster Invoker。下面來看一下該類的邏輯。

如上,FailoverClusterInvoker 的 doInvoke 方法首先是擷取重試次數,然後根據重試次數進行循環調用,失敗後進行重試。在 for 循環内,首先是通過負載均衡元件選擇一個 Invoker,然後再通過這個 Invoker 的 invoke 方法進行遠端調用。如果失敗了,記錄下異常,并進行重試。重試時會再次調用父類的 list 方法列舉 Invoker。整個流程大緻如此,不是很難了解。下面我們看一下 select 方法的邏輯。

如上,select 方法的主要邏輯集中在了對粘滞連接配接特性的支援上。首先是擷取 sticky 配置,然後再檢測 invokers 清單中是否包含 stickyInvoker,如果不包含,則認為該 stickyInvoker 不可用,此時将其置空。這裡的 invokers 清單可以看做是存活着的服務提供者清單,如果這個清單不包含 stickyInvoker,那自然而然的認為 stickyInvoker 挂了,是以置空。如果 stickyInvoker 存在于 invokers 清單中,此時要進行下一項檢測 ---- 檢測 selected 中是否包含 stickyInvoker。如果包含的話,說明 stickyInvoker 在此之前沒有成功提供服務(但其仍然處于存活狀态)。此時我們認為這個服務不可靠,不應該在重試期間内再次被調用,是以這個時候不會傳回該 stickyInvoker。如果 selected 不包含 stickyInvoker,此時還需要進行可用性檢測,比如檢測服務提供者網絡連通性等。當可用性檢測通過,才可傳回 stickyInvoker,否則調用 doSelect 方法選擇 Invoker。如果 sticky 為 true,此時會将 doSelect 方法選出的 Invoker 指派給 stickyInvoker。

以上就是 select 方法的邏輯,這段邏輯看起來不是很複雜,但是資訊量比較大。不搞懂 invokers 和 selected 兩個入參的含義,以及粘滞連接配接特性,這段代碼應該是沒法看懂的。大家在閱讀這段代碼時,不要忽略了對背景知識的了解。其他的不多說了,繼續向下分析。

doSelect 主要做了兩件事,第一是通過負載均衡元件選擇 Invoker。第二是,如果選出來的 Invoker 不穩定,或不可用,此時需要調用 reselect 方法進行重選。若 reselect 選出來的 Invoker 為空,此時定位 invoker 在 invokers 清單中的位置 index,然後擷取 index + 1 處的 invoker,這也可以看做是重選邏輯的一部分。關于負載均衡的選擇邏輯,我将會在下篇文章進行詳細分析。下面我們來看一下 reselect 方法的邏輯。

reselect 方法總結下來其實隻做了兩件事情,第一是查找可用的 Invoker,并将其添加到 reselectInvokers 集合中。第二,如果 reselectInvokers 不為空,則通過負載均衡元件再次進行選擇。其中第一件事情又可進行細分,一開始,reselect 從 invokers 清單中查找有效可用的 Invoker,若未能找到,此時再到 selected 清單中繼續查找。關于 reselect 方法就先分析到這,繼續分析其他的 Cluster Invoker。

FailbackClusterInvoker 會在調用失敗後,傳回一個空結果給服務提供者。并通過定時任務對失敗的調用進行重傳,适合執行消息通知等操作。下面來看一下它的實作邏輯。

這個類主要由3個方法組成,首先是 doInvoker,該方法負責初次的遠端調用。若遠端調用失敗,則通過 addFailed 方法将調用資訊存入到 failed 中,等待定時重試。addFailed 在開始階段會根據 retryFuture 為空與非,來決定是否開啟定時任務。retryFailed 方法則是包含了失敗重試的邏輯,該方法會對 failed 進行周遊,然後依次對 Invoker 進行調用。調用成功則将 Invoker 從 failed 中移除,調用失敗則忽略失敗原因。

以上就是 FailbackClusterInvoker 的執行邏輯,不是很複雜,繼續往下看。

FailfastClusterInvoker 隻會進行一次調用,失敗後立即抛出異常。适用于幂等操作,比如新增記錄。樓主日常開發中碰到過一次程式連續插入三條同樣的記錄問題,原因是新增記錄過程中包含了一些耗時操作,導緻接口逾時。而我當時使用的是 Dubbo 預設的 Cluster Invoker,即 FailoverClusterInvoker。其會在調用失敗後進行重試,是以導緻插入服務提供者插入了3條同樣的資料。如果當時考慮使用 FailfastClusterInvoker,就不會出現這種問題了。當然此時接口仍然會逾時,是以更合理的做法是使用 Dubbo 異步特性。或者優化服務邏輯,避免逾時。

其他的不多說了,下面直接看源碼吧。

上面代碼比較簡單了,首先是通過 select 方法選擇 Invoker,然後進行遠端調用。如果調用失敗,則立即抛出異常。FailfastClusterInvoker 就先分析到這,下面分析 FailsafeClusterInvoker。

FailsafeClusterInvoker 是一種失敗安全的 Cluster Invoker。所謂的失敗安全是指,當調用過程中出現異常時,FailsafeClusterInvoker 僅會列印異常,而不會抛出異常。Dubbo 官方給出的應用場景是寫入審計日志等操作,這個場景我在日常開發中沒遇到過,沒發言權,就不多說了。下面直接分析源碼。

FailsafeClusterInvoker 的邏輯和 FailfastClusterInvoker 的邏輯一樣簡單,是以就不多說了。繼續下面分析。

ForkingClusterInvoker 會在運作時通過線程池建立多個線程,并發調用多個服務提供者。隻要有一個服務提供者成功傳回了結果,doInvoke 方法就會立即結束運作。ForkingClusterInvoker 的應用場景是在一些對實時性要求比較高讀操作(注意是讀操作,并行寫操作可能不安全)下使用,但這将會耗費更多的服務資源。下面來看該類的實作。

ForkingClusterInvoker 的 doInvoker 方法比較長,這裡我通過兩個分割線将整個方法劃分為三個邏輯塊。從方法開始,到分割線1之間的代碼主要是用于選出 forks 個 Invoker,為接下來的并發調用提供輸入。分割線1和分割線2之間的邏輯主要是通過線程池并發調用多個 Invoker,并将結果存儲在阻塞隊列中。分割線2到方法結尾之間的邏輯主要用于從阻塞隊列中擷取傳回結果,并對傳回結果類型進行判斷。如果為異常類型,則直接抛出,否則傳回。

以上就是ForkingClusterInvoker 的 doInvoker 方法大緻過程。我在分割線1和分割線2之間的代碼上留了一個問題,問題是這樣的:為什麼要在 value >= selected.size() 的情況下,才将異常對象添加到阻塞隊列中?這裡來解答一下。原因是這樣的,在并行調用多個服務提供者的情況下,哪怕隻有一個服務提供者成功傳回結果,而其他全部失敗。此時 ForkingClusterInvoker 仍應該傳回成功的結果,而非抛出異常。在 value >= selected.size() 時将異常對象放入阻塞隊列中,可以保證異常對象不會出現在正常結果的前面,這樣可從阻塞隊列中優先取出正常的結果。

好了,關于 ForkingClusterInvoker 就先分析到這,接下來分析最後一個 Cluster Invoker。

本章的最後,我們再來看一下 BroadcastClusterInvoker。BroadcastClusterInvoker 會逐個調用每個服務提供者,如果其中一台報錯,在循環調用結束後,BroadcastClusterInvoker 會抛出異常。看官方文檔上的說明,該類通常用于通知所有提供者更新緩存或日志等本地資源資訊。這個使用場景筆者也沒遇到過,沒法詳細說明了,是以下面還是直接分析源碼吧。

以上就是 BroadcastClusterInvoker 的代碼,比較簡單,就不多說了。

本篇文章較為詳細的分析了 Dubbo 叢集容錯方面的内容,并詳細分析了叢集容錯的幾種實作方式。叢集容錯對于 Dubbo 架構來說,是很重要的邏輯。叢集子產品處于服務提供者和消費者之間,對于服務消費者來說,叢集可向其屏蔽服務提供者叢集的情況,使其能夠專心進行遠端調用。除此之外,通過叢集子產品,我們還可以對服務之間的調用鍊路進行編排優化,治理服務。總的來說,對于 Dubbo 而言,叢集容錯相關邏輯是非常重要的。想要對 Dubbo 有比較深的了解,叢集容錯是繞不過去的。是以,對于這部分内容,大家要認真看一下。

好了,本篇文章就先到這,感謝大家的閱讀。

時間

文章

2018-10-01

Dubbo 源碼分析 - SPI 機制

2018-10-13

Dubbo 源碼分析 - 自适應拓展原理

2018-10-31

Dubbo 源碼分析 - 服務導出

2018-11-12

Dubbo 源碼分析 - 服務引用

2018-11-17

Dubbo 源碼分析 - 叢集容錯之 Directory

2018-11-20

Dubbo 源碼分析 - 叢集容錯之 Router

2018-11-24

Dubbo 源碼分析 - 叢集容錯之 Cluster

本文在知識共享許可協定 4.0 下釋出,轉載需在明顯位置處注明出處 作者:田小波 本文同步釋出在我的個人部落格:http://www.tianxiaobo.com
Dubbo 源碼分析 - 叢集容錯之 Cluster

本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協定進行許可。