面試官:啥是集羣策略啊?

你好呀,我是 why。

之前有讀者問了 Dubbo Cluster 集羣的一些問題。

那麼本文聊一聊 Dubbo 的 Cluster 集羣和 Failover Cluster (失敗自動切換) 策略。

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

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

  • 1.Dubbo Cluster 集羣的作用是什麼?

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

    1. 默認的集羣實現類是什麼呢?
  • 4.Failover Cluster 調用失敗之後,會自動進行幾次重試呢?

    1. 什麼是 Dubbo 的粘滯連接?
    1. 粘滯連接在 Cluster 中是怎麼應用的?
  • 7.Cluster 選擇出一個可用的 Invoker 最多要進行幾次選擇?

    1. 請問幾次選擇分別是什麼?

注意:上面的 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

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 address="zookeeper://${zookeeper.address1}" weight="100" />

<dubbo:registry 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 最多要進行幾次選擇?

    1. 請問幾次選擇分別是什麼?

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 種才屬於集羣容錯的範疇。

另外的實現均有其他的應用場景。

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。

最後說一句

這一期的荒腔走板昨天發了,今天就不寫了,有興趣的可以看看呀:《今天,我在汶川跑馬拉松》

好了,看到了這裏安排個 “一鍵三連” (轉發、在看、點贊)吧,周更很累的,需要一點正反饋。

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

感謝您的閱讀,我堅持原創,十分歡迎並感謝您的關注。


我是 why,一個主要寫代碼,經常寫文章,偶爾拍視頻的程序猿。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nWQ8ZFppY5Ae74EU6pRQFg