Dubbo集羣容錯

Dubbo 定義了集羣接口 Cluster 以及 Cluster Invoker 來處理集羣容錯問題。

Cluster

Cluster接口的實現類有FailoverCluster、FailfastCluster、FailsafeCluster等,其作用都是通過join方法生成對應的invoker。

@SPI(FailoverCluster.NAME)
public interface Cluster {

    /**
     * Merge the directory invokers to a virtual invoker.
     *
     * @param <T>
     * @param directory
     * @return cluster invoker
     * @throws RpcException
     */
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;

}

AbstractClusterInvoker

AbstractClusterInvoker實現了Invoker接口,同時也是FairoverClusterInvoker、FairfastClusterInvoker、FairsafeClusterInvoker等Cluster Invoker的父類。在服務消費者進行遠程調用時,AbstractClusterInvoker 的 invoke 方法會被調用。列舉 Invoker、負載均衡、集羣容錯等操作均會在此階段被執行。

public abstract class AbstractClusterInvoker<T> implements Invoker<T> {
    ......

    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();

        // binding attachments into invocation.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
        // 列舉 Invoker
        List<Invoker<T>> invokers = list(invocation);
        // 加載 LoadBalance
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        // 調用 doInvoke 進行後續集羣容錯等操作(抽象方法,由子類實現)
        return doInvoke(invocation, invokers, loadbalance);
    }
    protected abstract Result doInvoke(Invocation invocation, List<Invoker<T>> invokers,
                                       LoadBalance loadbalance) throws RpcException;
    
    ......
}

集羣容錯方式

名稱 簡介
Failover

失敗自動切換:

當出現請求失敗時,會重試其它服務器。可以通過retries設置重試次數,默認爲2次。該方式是dubbo默認的容錯機制,適用於讀操作或冪等的寫操作。

Failfast

快速失敗:

當請求失敗後,快速返回異常結果,不做任何重試。適用於非冪等接口。

Failsafe

失敗安全:

當請求出現異常時,直接忽略異常。適用於佛系調用場景,即不關心調用是否成功,也不想影響外層的調用,例如不重要的日誌同步等。

Failback

失敗自動恢復:

請求失敗後,會自動記錄在失敗隊列中,並由一個定時線程池定時重試。適用於一些異步請求或最終一致性的請求。

Forking

並行調用:

同時調用多個相同的服務,只要有一個返回,則立即返回結果,可通過forks設置並行數。適用於某些對實時性要求極高的調用上,但也會浪費更多的資源。

Broadcast

廣播調用:

廣播調用所有可用的服務,任意一個節點報錯則報錯。適用於服務測試。

Available

最簡單的調用:

請求不會做負載均衡,遍歷所有服務列表,找到第一個可用的節點,直接請求並返回,如果沒有可用節點則拋出異常。

FailoverClusterInvoker.doInvoker

FailoverClusterInvoker是默認的Cluster Invoker,在doInvoker方法中實現集羣容錯。

首先是獲取重試次數,然後根據重試次數進行循環調用,失敗後進行重試。在 for 循環內,首先是通過負載均衡組件選擇一個 Invoker,然後再通過這個 Invoker 的 invoke 方法進行遠程調用。如果失敗了,記錄下異常,並進行重試。重試時會再次調用父類的 list 方法列舉 Invoker。

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        List<Invoker<T>> copyInvokers = invokers;
        checkInvokers(copyInvokers, invocation);
        String methodName = RpcUtils.getMethodName(invocation);
        // 獲取重試次數 DEFAULT_RETRIES 默認 2
        int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        RpcException le = null; // last exception.
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        // 循環調用,失敗重試
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                checkWhetherDestroyed();
                // 在進行重試前重新列舉 Invoker,這樣做的好處是,如果某個服務掛了,
                // 通過調用 list 可得到最新可用的 Invoker 列表
                copyInvokers = list(invocation);
                // check again
                checkInvokers(copyInvokers, invocation);
            }
            // 通過負載均衡選擇 Invoker
            Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
            // 添加到 invoker 到 invoked 列表中
            invoked.add(invoker);
            // 設置 invoked 到 RPC 上下文中
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                // 調用目標 Invoker 的 invoke 方法
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                    logger.warn("Although retry the method " + methodName
                            + " in the service " + getInterface().getName()
                            + " was successful by the provider " + invoker.getUrl().getAddress()
                            + ", but there have been failed providers " + providers
                            + " (" + providers.size() + "/" + copyInvokers.size()
                            + ") from the registry " + directory.getUrl().getAddress()
                            + " on the consumer " + NetUtils.getLocalHost()
                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
                            + le.getMessage(), le);
                }
                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(le.getCode(), "Failed to invoke the method "
                + methodName + " in the service " + getInterface().getName()
                + ". Tried " + len + " times of the providers " + providers
                + " (" + providers.size() + "/" + copyInvokers.size()
                + ") from the registry " + directory.getUrl().getAddress()
                + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
                + Version.getVersion() + ". Last error is: "
                + le.getMessage(), le.getCause() != null ? le.getCause() : le);
    }

 

發佈了166 篇原創文章 · 獲贊 199 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章