這是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相關的文章,有興趣的可以看一看:
才疏學淺,難免會有紕漏,如果你發現了錯誤的地方,還請你留言給我指出來,我對其加以修改。
感謝您的閱讀,原創不易,求個關注.
以上。
歡迎關注公眾號【why技術】,堅持輸出原創。願你我共同進步。