Dubbo的集羣容錯與負載均衡策略及自定義

Dubbo的集羣容錯策略spring

正常狀況下,當咱們進行系統設計時候,不只要考慮正常邏輯下代碼該如何走,還要考慮異常狀況下代碼邏輯應該怎麼走。當服務消費方調用服務提供方的服務出現錯誤時候,Dubbo提供了多種容錯方案,缺省模式爲failover,也就是失敗重試。緩存

Dubbo提供的集羣容錯模式安全

下面看下Dubbo提供的集羣容錯模式:服務器

Failover Cluster:失敗重試併發

當服務消費方調用服務提供者失敗後自動切換到其餘服務提供者服務器進行重試。這一般用於讀操做或者具備冪等的寫操做,須要注意的是重試會帶來更長延遲。可經過 retries="2" 來設置重試次數(不含第一次)。app

接口級別配置重試次數方法 <dubbo:reference retries="2" /> ,如上配置當服務消費方調用服務失敗後,會再重試兩次,也就是說最多會作三次調用,這裏的配置對該接口的全部方法生效。固然你也能夠針對某個方法配置重試次數以下:負載均衡

 

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

 

Failfast Cluster:快速失敗dom

當服務消費方調用服務提供者失敗後,當即報錯,也就是隻調用一次。一般這種模式用於非冪等性的寫操做。ide

Failsafe Cluster:失敗安全ui

當服務消費者調用服務出現異常時,直接忽略異常。這種模式一般用於寫入審計日誌等操做。

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,也就是每次隨機調用一臺服務提供者的機器。

Dubbo提供的負載均衡策略

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

在spring boot dubbo中,它能夠經過參數spring.dubbo.reference.loadbalance=consistentHash啓用,可是一致性哈希有一個並非很合理的地方,它是將相同參數的請求發送到相同提供者,默認爲第一個參數。若是全部接口的第一個參數爲公共入參還好,若是是採用泛型或繼承設計,這就比較悲劇了,咱們就遇到這個問題。分析源碼,可知它是調用com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance.ConsistentHashSelector#select生成的,以下:

        public Invoker<T> select(Invocation invocation) {
            String key = toKey(invocation.getArguments());
            byte[] digest = md5(key);
            Invoker<T> invoker = sekectForKey(hash(digest, 0));
            return invoker;
        }

        private String toKey(Object[] args) {
            StringBuilder buf = new StringBuilder();
            for (int i : argumentIndex) {
                if (i >= 0 && i < args.length) {
                    buf.append(args[i]);
                }
            }
            return buf.toString();
        }

因此,咱們徹底能夠經過將toKey暴露給業務自定義實現,以下:

        public Invoker<T> select(Invocation invocation) {
            String key = toKey(invocation.getArguments());

            // zjhua 自定義一致性哈希判斷用的key,用於指定請求發送到特定節點
            if (rpcLoadBalance == null) {
                rpcLoadBalance = SpringContextHolder.getBean(RpcLoadBalance.class);
            }

            if (rpcLoadBalance != null) {
                key = rpcLoadBalance.toKey(invocation,invocation.getArguments());
            }
            byte[] digest = md5(key);
            Invoker<T> invoker = sekectForKey(hash(digest, 0));
            return invoker;
        }

這樣就能夠根據特定接口/參數值業務本身決定是否須要將請求發送到相同節點了。

相關文章
相關標籤/搜索