1. 程式人生 > 其它 >Dubbo的叢集容錯與負載均衡策略

Dubbo的叢集容錯與負載均衡策略

Dubbo的叢集容錯策略
正常情況下,當我們進行系統設計時候,不僅要考慮正常邏輯下程式碼該如何走,還要考慮異常情況下程式碼邏輯應該怎麼走。當服務消費方呼叫服務提供方的服務出現錯誤時候,Dubbo提供了多種容錯方案,預設模式為failover,也就是失敗重試。
Failover Cluster:失敗重試
當服務消費方呼叫服務提供者失敗後自動切換到其他服務提供者伺服器進行重試。這通常用於讀操作或者具有冪等的寫操作,需要注意的是重試會帶來更長延遲。可通過 retries="2" 來設定重試次數(不含第一次)。
介面級別配置重試次數方法 <dubbo:reference retries="2" /> ,如上配置當服務消費方呼叫服務失敗後,會再重試兩次,也就是說最多會做三次呼叫,這裡的配置對該介面的所有方法生效。當然你也可以針對某個方法配置重試次數如下:

<dubbo:reference>
    <dubbo:method name="sayHello" retries="2" />
</dubbo:reference>

Failfast Cluster:快速失敗
當服務消費方呼叫服務提供者失敗後,立即報錯,也就是隻呼叫一次。通常這種模式用於非冪等性的寫操作。
Failsafe Cluster:失敗安全
當服務消費者呼叫服務出現異常時,直接忽略異常。這種模式通常用於寫入審計日誌等操作。
Failback Cluster:失敗自動恢復
當服務消費端用服務出現異常後,在後臺記錄失敗的請求,並按照一定的策略後期再進行重試。這種模式通常用於訊息通知操作。


Forking Cluster:並行呼叫
當消費方呼叫一個介面方法後,Dubbo Client會並行呼叫多個服務提供者的服務,只要一個成功即返回。這種模式通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2" 來設定最大並行數。
Broadcast Cluster:廣播呼叫
當消費者呼叫一個介面方法後,Dubbo Client會逐個呼叫所有服務提供者,任意一臺呼叫異常則這次呼叫就標誌失敗。這種模式通常用於通知所有提供者更新快取或日誌等本地資源資訊。
如上,Dubbo本身提供了豐富的叢集容錯模式,但是如果您有定製化需求,可以根據Dubbo提供的擴充套件介面Cluster進行定製。在後面的消費方啟動流程章節會講解何時/如何使用的叢集容錯。

失敗重試策略實現分析
Dubbo中具體實現失敗重試的是FailoverClusterInvoker類,這裡我們看下具體實現,主要看下doInvoke程式碼:

public Result doInvoke(Invocation invocation,final List<Invoker<T>> invokers,LoadBalance loadbalance) throws RpcException{
    // (1) 所有服務提供者
    List<Invoker<T>> copyinvokers = invokers;
    checkInvokers(copyinvokers,invocation);

    // (2)獲取重試次數
    int len  = getUrl().getMethodParameter(invocation.getMethodName(),Constants.RETRIES_KEY,Constants.DEFAULT_RETRIES) + 1;
    if(len <= 0){
        len = 1;
    }
    
    // (3)使用迴圈,失敗重試
    RpcException le = null;    // last exception
    List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size());
    Set<String> providers = new HashSet<String>();
    for(int i=0;i<len;i++){
        // 重試時,進行重新選擇,避免重試時invoker列表已發生變化
        // 注意:如果列表發生了變化,那麼invoked判斷會失效,因為invoker示例已經改變
        if(i > 0){
            // (3.1)
            checkWhetherDestroyed();    // 如果當前例項已經被銷燬,則丟擲異常
            // (3.2) 重新獲取所有服務提供者
            copyinvokers = list(invocation);
            // (3.3) 重新檢查一下
            checkInvokers(copyinvokers,invocation);
        }
        // (3.4) 選擇負載均衡策略
        Invoker<T> invoker = select(loadbalance,invocation,copyinvokers,invoked);
        invoked.add(invoker);
        RpcContext.getContext().setInvokers((List)invoked);
        // (3.5) 具體發起遠端呼叫
        try{
            Result result = invoker.invoke(invocation);
            if(le != null && logger.isWarnEnabled()){
                ...
            }
            return result;
        }catch(RpcException e){
            if(e.isBiz()){    // biz exception
                throw e;
            }
            le = e;
        }catch(Throwable e){
            le = new RpcException(e.getMessage(),e);
        }finally{
            providers.add(invoker.getUrl().getAddress());
        }
    }
    throw new RpcException("丟擲異常...");
}
  • 如上程式碼(2)從url引數裡面獲取設定的重試次數,如果使用者沒有設定則取預設的值,預設是重試2,這裡需要注意的是程式碼(2)是獲取配置重試次數又+1了。這說明 總共呼叫次數=重試次數+1 (1是正常呼叫)。
  • 程式碼(3)迴圈重複試用,如果第一次呼叫成功則直接跳出迴圈返回,否則迴圈重試。第一次呼叫時不會走程式碼(3.1)(3.2)(3.3)。如果第一次調用出現異常,則會迴圈,這時候i=1,所以會執行程式碼(3.1)檢查是否有執行緒呼叫了當前ReferenceConfig的destroy()方法,銷燬了當前消費者。如果當前消費者例項已經被消費,那麼重試就沒有意義了,所以會丟擲RpcException異常。
  • 如果當前消費者例項沒被銷燬,則執行程式碼(3.2)重新獲取當前服務提供者列表,這是因為從第一次調開始到執行緒可能提供者列表已經變化了,獲取列表後,然後執行(3.2)又一次進行了校驗。校驗通過則執行(3.4),根據負載均衡策略選擇一個服務提供者,再次嘗試呼叫。負載均衡策略的選擇下節會講解。

Dubbo的負載均衡策略
當服務提供方是叢集的時候,為了避免大量請求一直落到一個或幾個服務提供方機器上,從而使這些機器負載很高,甚至打死,需要做一定的負載均衡策略。Dubbo提供了多種均衡策略,預設為random,也就是每次隨機呼叫一臺服務提供者的機器。

  • Random LoadBalance:隨機策略。按照概率設定權重,比較均勻,並且可以動態調節提供者的權重。
  • RoundRobin LoadBalance:輪詢策略。輪詢,按公約後的權重設定輪詢比率。會存在執行比較慢的服務提供者堆積請求的情況,比如一個機器執行的非常慢,但是機器沒有掛呼叫(如果掛了,那麼當前機器會從Zookeeper的服務列表刪除),當很多新的請求到達該機器後,由於之前的請求還沒有處理完畢,會導致新的請求被堆積,久而久之,所有消費者呼叫這臺機器上的請求都被阻塞。
  • LeastActive LoadBalance:最少活躍呼叫數。如果每個提供者的活躍數相同,則隨機選擇一個。在每個服務提供者裡面維護者一個活躍數計數器,用來記錄當前同時處理請求的個數,也就是併發處理任務的個數。所以如果這個值越小說明當前服務提供者處理的速度很快或者當前機器的負載比較低,所以路由選擇時候就選擇該活躍度最小的機器。如果一個服務提供者處理速度很慢,由於堆積,那麼同時處理的請求就比較多,也就是活躍呼叫數目越大,這也使得慢的提供者收到更少請求,因為越慢的提供者的活躍度越來越大。
  • ConsistentHash LoadBalance:一致性Hash策略。一致性Hash,可以保證相同引數的請求總是發到同一提供者,當某一臺提供者掛了時,原本發往該提供者的請求,基於虛擬節點,平攤到其他提供者,不會引起劇烈變動。