Dubbo源碼學習15

本篇幅分析Dubbo的集羣Cluster以及ClusterInvoker的實現。爲了避免單點故障,現在的應用通常至少會部署在兩臺服務器上。對於一些負載比較高的服務,會部署更多的服務器。這樣,在同一環境下的服務提供者數量會大於1。對於服務消費者來說,同一環境下出現了多個服務提供者。這時會出現一個問題,服務消費者需要決定選擇哪個服務提供者進行調用。另外服務調用失敗時的處理措施也是需要考慮的,是重試呢,還是拋出異常,亦或是隻打印異常等。爲了處理這些問題,Dubbo 定義了集羣接口 Cluster 以及 Cluster Invoker。集羣 Cluster 用途是將多個服務提供者合併爲一個 Cluster Invoker,並將這個 Invoker 暴露給服務消費者。這樣一來,服務消費者只需通過這個 Invoker 進行遠程調用即可,至於具體調用哪個服務提供者,以及調用失敗後如何處理等問題,現在都交給集羣模塊去處理。集羣模塊是服務提供者和服務消費者的中間層,爲服務消費者屏蔽了服務提供者的情況,這樣服務消費者就可以專心處理遠程調用相關事宜。比如發請求,接受服務提供者返回的數據等。這就是集羣的作用。Dubbo 提供了多種集羣實現,包含但不限於 Failover Cluster、Failfast Cluster 和 Failsafe Cluster 等。每種集羣實現類的用途不同,接下來會一一進行分析。

Cluster、Invoker、Directory、Router、LoadBalance

集羣工作過程可以分爲兩個階段,第一個階段是服務消費者初始化階段,集羣Cluster實現類爲服務消費者創建ClusterInvoker實例,即merge操作。第二個節點是服務消費者在進行遠程調用時。以FailoverClusterInvoker爲例,該類型Cluster Invoker首先會調用Directory的list方法列舉出Invoker列表。Directory的作用是保存List<Invoker>,RegistryDirectory是一個動態的服務目錄,他感知到註冊中心配置變化,他持有的invoker列表會隨着註冊中心的內容變化而變化。每次變化RegistryDirectory都會動態增刪Invoker,並調用Router的route方法進行路由,當然Configurator的configurator進行服務配置的更改。當 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表後,它會通過 LoadBalance 從 Invoker 列表中選擇一個 Invoker。最後 FailoverClusterInvoker 會將參數傳給 LoadBalance 選擇出的 Invoker 實例的 invoker 方法,進行真正的遠程調用。

Cluster

Dubbo的SPI擴展接口,Cluster用於創建ClusterInvoker實例,

                                                                                                 Cluster接口的實現類

Cluser$Adaptive.java

package com.alibaba.dubbo.rpc.cluster;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Cluster$Adaptive implements com.alibaba.dubbo.rpc.cluster.Cluster {
    public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("cluster", "failover");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString() + ") use keys([cluster])");
        com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
        return extension.join(arg0);
    }
}

根據Cluster$Adaptive.join方法我們不難得知默認cluster爲FailoverCluster

FailoverCluster.java

public class FailoverCluster implements Cluster {

    public final static String NAME = "failover";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<T>(directory);
    }

}

FailbackCluster.java

public class FailbackCluster implements Cluster {

    public final static String NAME = "failback";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailbackClusterInvoker<T>(directory);
    }

}

其他的Cluster實現類都和FailoverCluster、FailbackCluster實現類似,僅僅就是創建了Cluster Invoker對象。

Cluster Invoker

我們首先從各種 Cluster Invoker 的父類 AbstractClusterInvoker 源碼開始說起。前面說過,集羣工作過程可分爲兩個階段,第一個階段是在服務消費者初始化期間。第二個階段是在服務消費者進行遠程調用時,此時 AbstractClusterInvoker 的 invoke 方法會被調用。列舉 Invoker,負載均衡等操作均會在此階段被執行。因此下面先來看一下 invoke 方法的邏輯。

AbstractClusterInvoker.invoke(final Invocation invocation)

@Override
    public Result invoke(final Invocation invocation) throws RpcException {
        //通過destroyed變量判斷是否已被銷燬
        checkWhetherDestroyed();
        //負載均衡策略
        LoadBalance loadbalance = null;

        //獲取當前線程的rpcContext中的attachments變量到RpcInvocation中
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
        //從directory列出Invoker列表
        List<Invoker<T>> invokers = list(invocation);
        //invokers非空
        if (invokers != null && !invokers.isEmpty()) {
            //通過dubbo的spi機制加載負載均衡策略LoadBalance
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    //獲取方法級別的負載均衡配置如果沒獲取到則取服務級別的負載均衡策略
                    //<dubbo:reference interface="..." loadbalance="roundrobin" />
                    //<dubbo:method name="..." loadbalance="roundrobin"/>
                    .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
        }
        //如果是異步調用給此次調用生產全局的id
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        //委託子類實現真正的調用邏輯
        return doInvoke(invocation, invokers, loadbalance);
    }

AbstractClusterInvoker 的 invoke 方法主要用於列舉 Invoker,以及加載 LoadBalance。最後再調用模板方法 doInvoke 進行後續操作。下面我們來看一下 Invoker 列舉方法 list(Invocation) 的邏輯

AbstractClusterInvoker.list(Invocation invocation)

protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
        List<Invoker<T>> invokers = directory.list(invocation);
        return invokers;
    }

委託Directory的list方法獲取List<Invoker<T>>的列表。

AbstractDirectory.list(Invocation invocation)

@Override
    public List<Invoker<T>> list(Invocation invocation) throws RpcException {
        if (destroyed) {
            throw new RpcException("Directory already destroyed .url: " + getUrl());
        }
        //調用子類doList方法獲取invokers列表
        List<Invoker<T>> invokers = doList(invocation);
        List<Router> localRouters = this.routers;
        if (localRouters != null && !localRouters.isEmpty()) {
            for (Router router : localRouters) {
                try {
                    // 獲取 runtime 參數,並根據參數決定是否進行路由
                    //Router 的 runtime 參數這裏簡單說明一下,這個參數決定了是否在每次調用服務時都執行路由規則。如果 runtime 爲 true,那麼每次調用服務前,都需要進行服務路由。
                    if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
                        invokers = router.route(invokers, getConsumerUrl(), invocation);
                    }
                } catch (Throwable t) {
                    logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
                }
            }
        }
        return invokers;
    }

上面就是 AbstractDirectory 的 list 方法源碼,這個方法封裝了 Invoker 的列舉過程。如下:

  1. 調用 doList 獲取 Invoker 列表
  2. 根據 Router 的 getUrl 返回值爲空與否,以及 runtime 參數決定是否進行服務路由

以上步驟中,doList 是模板方法,需由子類實現。

RegistryDirectory.doList(Invocation invocation)

@Override
    public List<Invoker<T>> doList(Invocation invocation) {
        // 服務提供者關閉或禁用了服務,此時拋出 No provider 異常
        if (forbidden) {
            // 1. No service provider 2. Service providers are disabled
            throw new RpcException(RpcException.FORBIDDEN_EXCEPTION,
                    "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost()
                            + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist).");
        }
        List<Invoker<T>> invokers = null;
        //method 對應的Invoker緩存表。
        Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap;
        if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
            //獲取方法名稱和參數
            String methodName = RpcUtils.getMethodName(invocation);
            Object[] args = RpcUtils.getArguments(invocation);
            //檢測參數列表的第一個參數是否爲 String 或 enum 類型
            if (args != null && args.length > 0 && args[0] != null
                    && (args[0] instanceof String || args[0].getClass().isEnum())) {
                // 通過 方法名 + 第一個參數名稱 查詢 Invoker 列表,具體的使用場景暫時沒想到
                invokers = localMethodInvokerMap.get(methodName + "." + args[0]);
            }
            if (invokers == null) {
                //通過方法名稱獲取invokers
                invokers = localMethodInvokerMap.get(methodName);
            }
            if (invokers == null) {
                //通過* 獲取invokers
                invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
            }
            //冗餘邏輯,pull request #2861 移除了下面的 if 分支代碼
            if (invokers == null) {
                Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
                if (iterator.hasNext()) {
                    invokers = iterator.next();
                }
            }
        }
        return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
    }

上述方法就是從localMethodInvokerMap中根據方法名稱去獲取List<Invoker>,普通的調用可通過方法名獲取到對應的 Invoker 列表,泛化調用可通過 ***** 獲取到 Invoker 列表。localMethodInvokerMap 源自 RegistryDirectory 類的成員變量 methodInvokerMap(我們在Dubbo源碼學習12)分析過了methodInvokerMap的初始化。

FailoverClusterInvoker

失敗自動切換,當出現失敗,重試其它服務器。可以通過<dubbo:service retries="2" />或者<dubbo:reference retries="2" />或者<dubbo:reference> <dubbo:method name="findFoo" retries="2" /> </dubbo:reference>方式配置重試次數。

public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {

    private static final Logger logger = LoggerFactory.getLogger(FailoverClusterInvoker.class);

    public FailoverClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        //invokers的拷貝
        List<Invoker<T>> copyinvokers = invokers;
        //invokers非空判斷
        checkInvokers(copyinvokers, invocation);
        //獲取重試次數,methodName.retries或者retries的值加1 默認值爲2+1 = 3 次
        int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        //上一次發生的異常
        RpcException le = null;
        //已經調用過的invoker列表
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size());
        Set<String> providers = new HashSet<String>(len);
        for (int i = 0; i < len; i++) {
            // 在進行重試前重新列舉 Invoker,這樣做的好處是,如果某個服務掛了,
            // 通過調用 list 可得到最新可用的 Invoker 列表
            if (i > 0) {
                checkWhetherDestroyed();
                copyinvokers = list(invocation);
                // check again
                checkInvokers(copyinvokers, invocation);
            }
            //通過loadBalance選擇Invoker
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, 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 " + invocation.getMethodName()
                            + " 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) {
                // 判斷是否爲dubbo的業務異常,是的話拋出
                if (e.isBiz()) {
                    throw e;
                }
                //賦值le
                le = e;
            } catch (Throwable e) {
                //賦值le
                le = new RpcException(e.getMessage(), e);
            } finally {
                //添加提供者的url到providers用於拋出異常
                providers.add(invoker.getUrl().getAddress());
            }
        }
        throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
                + invocation.getMethodName() + " 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 != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
    }

}

FailoverClusterInvoker 的 doInvoke 方法首先是獲取重試次數,然後根據重試次數進行循環調用,失敗後進行重試。在 for 循環內,首先是通過負載均衡組件選擇一個 Invoker,然後再通過這個 Invoker 的 invoke 方法進行遠程調用。如果失敗了,記錄下異常,並進行重試。重試時會再次調用父類的 list 方法列舉 Invoker。整個流程大致如此,不是很難理解。下面我們看一下 select 方法的邏輯。

AbstractClusterInvoker.select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected)

/**
     * 使用負載平衡策略選擇一個調用程序。</br>
     * a)首先,使用loadbalance選擇一個調用程序。 如果此調用程序在先前選擇的列表中,
     * 或者如果此調用程序不可用,則繼續執行步驟b(重新選擇),否則返回第一個選定的調用程序</br>
     * b)此規則確保所選調用者有最小的機會選擇爲先前已選擇的列表,並且還保證此調用者可用
     *
     * @param loadbalance load balance policy
     * @param invocation
     * @param invokers    invoker candidates
     * @param selected    exclude selected invokers or not
     * @return
     * @throws RpcException
     */
    protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
        if (invokers == null || invokers.isEmpty())
            return null;
        //調用的方法名稱
        String methodName = invocation == null ? "" : invocation.getMethodName();
        //獲取sticky 或者methodName.sticky屬性,sticky 表示粘滯連接。所謂粘滯連接是指讓服務消費者儘可能的
        //調用同一個服務提供者,除非該提供者掛了再進行切換
        boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY);
        {
            // 檢測 invokers 列表是否包含 stickyInvoker,如果不包含,
            // 說明 stickyInvoker 代表的服務提供者掛了,此時需要將其置空
            if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
                stickyInvoker = null;
            }
            //如果sticky屬性爲true,並且stickyInvoker不爲null
            //如果
            if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
                if (availablecheck && stickyInvoker.isAvailable()) {
                    return stickyInvoker;
                }
            }
        }
        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
        // 如果 sticky 爲 true,則將負載均衡組件選出的 Invoker 賦值給 stickyInvoker
        if (sticky) {
            stickyInvoker = invoker;
        }
        return invoker;
    }

上述方法主要做了兩件事,粘滯stickyInvoker的處理,粘滯連接用於有狀態服務,儘可能讓客戶端總是向同一提供者發起調用,除非該提供者掛了,再連另一臺。調用doSelect方法獲取一個invoker,並判斷是否支持stick,支持sticky則將當前invoker賦值到AbstractClusterInvoker的stickyInvoker成員變量中。

AbstractClusterInvoker.doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected)

private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
        if (invokers == null || invokers.isEmpty())
            return null;
        if (invokers.size() == 1)
            return invokers.get(0);
        if (loadbalance == null) {
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
        }
        //通過負載均衡loadBalance選出一個invoker
        Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

        //如果“調用者”在“選定”中,或者調用者不可用&& availablecheck爲true,請重新選擇。
        if ((selected != null && selected.contains(invoker))
                || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
            try {
                Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
                if (rinvoker != null) {
                    invoker = rinvoker;
                } else {
                    //檢查當前選定的調用者的索引,如果不是最後一個,則在index + 1處選擇一個.
                    int index = invokers.indexOf(invoker);
                    try {
                        //避免碰撞
                        invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0);
                    } catch (Exception e) {
                        logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
                    }
                }
            } catch (Throwable t) {
                logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
            }
        }
        return invoker;
    }

第一是通過負載均衡組件選擇 Invoker。第二是,如果選出來的 Invoker 不穩定,或不可用,此時需要調用 reselect 方法進行重選。若 reselect 選出來的 Invoker 爲空,此時定位 invoker 在 invokers 列表中的位置 index,然後獲取 index + 1 處的 invoker,這也可以看做是重選邏輯的一部分

AbstractClusterInvoker.reselect(LoadBalance loadbalance, Invocation invocation,                             List<Invoker<T>> invokers, List<Invoker<T>> selected, boolean availablecheck)

private Invoker<T> reselect(LoadBalance loadbalance, Invocation invocation,
                                List<Invoker<T>> invokers, List<Invoker<T>> selected, boolean availablecheck)
            throws RpcException {

        //預先分配一個,肯定會使用此列表.
        List<Invoker<T>> reselectInvokers = new ArrayList<Invoker<T>>(invokers.size() > 1 ? (invokers.size() - 1) : invokers.size());

        //根據 availablecheck 進行不同的處理
        if (availablecheck) {
            for (Invoker<T> invoker : invokers) {
                if (invoker.isAvailable()) {
                    //添加invoker到reselectInvokers
                    if (selected == null || !selected.contains(invoker)) {
                        reselectInvokers.add(invoker);
                    }
                }
            }
            //排除掉不可用的invoker和已經選過的invoker後,使用loadBalance進行重新選擇
            if (!reselectInvokers.isEmpty()) {
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }
        } else {
            //不檢查Invoker的可用性
            for (Invoker<T> invoker : invokers) {
                if (selected == null || !selected.contains(invoker)) {
                    reselectInvokers.add(invoker);
                }
            }
            //排除掉已selected的invoker使用負載均衡策略選擇
            if (!reselectInvokers.isEmpty()) {
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }
        }
        //所有的invokers都已經selected了,說白了reselectedInvokers始終爲空集合
        //這裏從 selected 列表中查找可用的 Invoker,並將其添加到 reselectInvokers 集合中
        {
            if (selected != null) {
                for (Invoker<T> invoker : selected) {
                    //選擇可用的
                    if ((invoker.isAvailable())
                            && !reselectInvokers.contains(invoker)) {
                        reselectInvokers.add(invoker);
                    }
                }
            }
            //通過負載均衡策略選擇
            if (!reselectInvokers.isEmpty()) {
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }
        }
        return null;
    }

reselect 方法總結下來其實只做了兩件事情,第一是查找可用的 Invoker,並將其添加到 reselectInvokers 集合中。第二,如果 reselectInvokers 不爲空,則通過負載均衡組件再次進行選擇。其中第一件事情又可進行細分,一開始,reselect 從 invokers 列表中查找有效可用的 Invoker,若未能找到,此時再到 selected 列表中繼續查找。關於 reselect 方法就先分析到這,繼續分析其他的 Cluster Invoker。

FailbackClusterInvoker

失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。

public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {

    private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class);
    /**
     * 錯誤重試周期
     */
    private static final long RETRY_FAILED_PERIOD = 5 * 1000;

    /**
     * Use {@link NamedInternalThreadFactory} to produce {@link com.alibaba.dubbo.common.threadlocal.InternalThread}
     * which with the use of {@link com.alibaba.dubbo.common.threadlocal.InternalThreadLocal} in {@link RpcContext}.
     */
    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2,
            new NamedInternalThreadFactory("failback-cluster-timer", true));
    /**
     * 失敗的集合
     */
    private final ConcurrentMap<Invocation, AbstractClusterInvoker<?>> failed = new ConcurrentHashMap<Invocation, AbstractClusterInvoker<?>>();

    private volatile ScheduledFuture<?> retryFuture;

    public FailbackClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    private void addFailed(Invocation invocation, AbstractClusterInvoker<?> router) {
        //還未開始調度,加鎖調度
        if (retryFuture == null) {
            synchronized (this) {
                if (retryFuture == null) {
                    //使用線程池定時重試
                    retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                //重試
                                retryFailed();
                            } catch (Throwable t) { // Defensive fault tolerance
                                logger.error("Unexpected error occur at collect statistic", t);
                            }
                        }
                    }, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS);
                }
            }
        }
        //保存到failed集合中
        failed.put(invocation, router);
    }

    void retryFailed() {
        if (failed.size() == 0) {
            return;
        }
        //
        for (Map.Entry<Invocation, AbstractClusterInvoker<?>> entry : new HashMap<Invocation, AbstractClusterInvoker<?>>(
                failed).entrySet()) {
            // 再次進行調用
            Invocation invocation = entry.getKey();
            Invoker<?> invoker = entry.getValue();
            try {
                invoker.invoke(invocation);
                //調用成功刪除invocation
                failed.remove(invocation);
            } catch (Throwable e) {
                logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
            }
        }
    }

    @Override
    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            //invokers列表檢查
            checkInvokers(invokers, invocation);
            //選擇invoker
            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
            //使用invoker調用
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
                    + e.getMessage() + ", ", e);
            addFailed(invocation, this);
            return new RpcResult(); // ignore
        }
    }

}

這個類主要由3個方法組成,首先是 doInvoker,該方法負責初次的遠程調用。若遠程調用失敗,則通過 addFailed 方法將調用信息存入到 failed 中,等待定時重試。addFailed 在開始階段會根據 retryFuture 爲空與否,來決定是否開啓定時任務。retryFailed 方法則是包含了失敗重試的邏輯,該方法會對 failed 進行遍歷,然後依次對 Invoker 進行調用。調用成功則將 Invoker 從 failed 中移除,調用失敗則忽略失敗原因。

FailfastClusterInvoker

快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。

public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {

    public FailfastClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        //選擇一個invoker
        checkInvokers(invokers, invocation);
        Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
        try {
            //調用
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            //拋出異常
            if (e instanceof RpcException && ((RpcException) e).isBiz()) {
                throw (RpcException) e;
            }
            throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName() + " select from all providers " + invokers + " for service " + getInterface().getName() + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);
        }
    }
}

FailsafeClusterInvoker

失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。

public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {
    private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class);

    public FailsafeClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            checkInvokers(invokers, invocation);
            //選擇一個invoker調用
            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            //記錄錯誤日誌
            logger.error("Failsafe ignore exception: " + e.getMessage(), e);
            //返回空的結果
            return new RpcResult();
        }
    }
}

ForkingClusterInvoker

並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2" 來設置最大並行數。

public class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> {

    /**
     * Use {@link NamedInternalThreadFactory} to produce {@link com.alibaba.dubbo.common.threadlocal.InternalThread}
     * which with the use of {@link com.alibaba.dubbo.common.threadlocal.InternalThreadLocal} in {@link RpcContext}.
     */
    private final ExecutorService executor = Executors.newCachedThreadPool(
            new NamedInternalThreadFactory("forking-cluster-timer", true));

    public ForkingClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            //檢查invokers
            checkInvokers(invokers, invocation);
            //已選擇的
            final List<Invoker<T>> selected;
            //獲取並行數量fokrs配置默認爲2
            final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS);
            //獲取超時配置默認爲1000ms
            final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
            //如果forks數量不合理,直接賦值invokers到selected
            if (forks <= 0 || forks >= invokers.size()) {
                selected = invokers;
            } else {
                //
                selected = new ArrayList<Invoker<T>>();
                for (int i = 0; i < forks; i++) {
                    //循環選出forks數量個invoker,放入selected中
                    Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
                    if (!selected.contains(invoker)) {
                        selected.add(invoker);
                    }
                }
            }
            //設置上下文
            RpcContext.getContext().setInvokers((List) selected);
            final AtomicInteger count = new AtomicInteger();
            final BlockingQueue<Object> ref = new LinkedBlockingQueue<Object>();
            //使用線程池分別執行selected invoker列表
            for (final Invoker<T> invoker : selected) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //調用
                            Result result = invoker.invoke(invocation);
                            //結果加入到阻塞隊列
                            ref.offer(result);
                        } catch (Throwable e) {
                            //如果拋出異常,那也要等到所有的selected的invoker調用都完成了,
                            //再講異常結果放入隊列,不然萬一第一個失敗了最後一個成功了呢?
                            int value = count.incrementAndGet();
                            if (value >= selected.size()) {
                                ref.offer(e);
                            }
                        }
                    }
                });
            }
            try {
                //超時獲取結果,阻塞隊列有結果了,結果會返回
                Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
                //如果是Throwable類型的記過,拋出異常
                if (ret instanceof Throwable) {
                    Throwable e = (Throwable) ret;
                    throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);
                }
                return (Result) ret;
            } catch (InterruptedException e) {
                throw new RpcException("Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e);
            }
        } finally {
            // 清除綁定到當前線程的附加參數
            RpcContext.getContext().clearAttachments();
        }
    }
}

ForkingClusterInvoker 的 doInvoker 方法比較長,首先選出forks數量個invoker。然後通過線程池併發調用多個 Invoker,並將結果存儲在阻塞隊列中。最後從阻塞隊列中獲取返回結果,並對返回結果類型進行判斷。如果爲異常類型,則直接拋出,否則返回。

爲什麼要在value >= selected.size()的情況下,纔將異常對象添加到阻塞隊列中?這裏來解答一下。原因是這樣的,在並行調用多個服務提供者的情況下,只要有一個服務提供者能夠成功返回結果,而其他全部失敗。此時 ForkingClusterInvoker 仍應該返回成功的結果,而非拋出異常。在value >= selected.size()時將異常對象放入阻塞隊列中,可以保證異常對象不會出現在正常結果的前面,這樣可從阻塞隊列中優先取出正常的結果。

BroadcastClusterInvoker

廣播調用所有提供者,逐個調用,任意一臺報錯則報錯。通常用於通知所有提供者更新緩存或日誌等本地資源信息。

/**
 * 挨個調用invoker
 *
 */
public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {

    private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class);

    public BroadcastClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        checkInvokers(invokers, invocation);
        RpcContext.getContext().setInvokers((List) invokers);
        RpcException exception = null;
        Result result = null;
        for (Invoker<T> invoker : invokers) {
            try {
                result = invoker.invoke(invocation);
            } catch (RpcException e) {
                exception = e;
                logger.warn(e.getMessage(), e);
            } catch (Throwable e) {
                exception = new RpcException(e.getMessage(), e);
                logger.warn(e.getMessage(), e);
            }
        }
        //如果異常不爲空 拋出異常
        if (exception != null) {
            throw exception;
        }
        return result;
    }

}

...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章