dubbo原始碼分析之叢集Cluster

jy02268879發表於2020-11-05

一、簡介

Cluster主要兩個作用

1.將多個服務提供者合併為一個 Cluster Invoker,並將這個 Invoker 暴露給服務消費者。叢集模組是服務提供者和服務消費者的中間層,為服務消費者遮蔽了服務提供者的情況。

服務A呼叫服務B,服務B有5臺機器,Cluster會決定到底呼叫服務B的哪臺機器。服務A不用關心這個。

2.叢集容錯。

服務呼叫失敗的時候,做什麼處理(重試?丟擲異常?僅僅列印異常日誌等等)

dubbo中叢集容錯相關的元件有:Cluster、Cluster Invoker、Directory、Router 和 LoadBalance 等

Cluster相關主要的兩個類

1.AbstractClusterInvoker類

遠端呼叫失敗後的的處理邏輯均是封裝在 Cluster Invoker 中

2.Cluster介面

用於生成 Cluster Invoker

該介面的實現類,代表了各種不同的容錯方式

  • Failover Cluster - 失敗自動切換
  • Failfast Cluster - 快速失敗
  • Failsafe Cluster - 失敗安全
  • Failback Cluster - 失敗自動恢復
  • Forking Cluster - 並行呼叫多個服務提供者

 Cluster工作過程

第一個階段

在服務消費者初始化期間。

叢集 Cluster 實現類為服務消費者建立 Cluster Invoker 例項

第二個階段

在服務消費者進行遠端呼叫時。

(以 FailoverClusterInvoker 為例)

1.FailoverClusterInvoker 會呼叫 Directory 的 list 方法列舉 Invoker 列表。(這裡得到的列表是已經經過Router 的 route 方法過濾掉了不符合路由規則的了

2.FailoverClusterInvoker 通過 LoadBalance 從 Invoker 列表中選擇一個 Invoker。(這裡得到的一個Invoker就是最後要呼叫的那個了,這已經做了LoadBalance 負載均衡了

3. FailoverClusterInvoker 呼叫最終選定出來的 Invoker 例項的 invoke 方法,進行真正的遠端呼叫

二、原始碼分析

 1.FailoverCluster

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);
    }

}

其實就是做了一件事,建立FailoverClusterInvoker

什麼時候觸發呢?

 在啟動的時候注入帶有@Reference的bean,要建立代理的時候,大致呼叫觸發是:

RegistryProtocol.refer--------> failoverCluster.join

換句話說,建立服務代理是根據ClusterInvoker來建立的,所以這裡會呼叫某個Cluster的join方法生成一個ClusterInvoker

2.FailoverClusterInvoker

 FailoverClusterInvoker 在呼叫失敗時,會自動切換 Invoker 進行重試。預設配置下,Dubbo 會使用這個類作為預設 Cluster Invoker。

注意:這個doInvoke其實是個模板方法,是父類AbstractClusterInvoker.invoke呼叫的它,AbstractClusterInvoker.invoke中呼叫了Directory的list方法得到了一組符合路由規則的Invoker,然後把這組invoker作為引數呼叫子類FailoverClusterInvoker.doInvoker的入參。

2.2 doInvoke方法 

觸發這個方法具體的呼叫鏈,等到後面一章講服務呼叫的時候詳細講 

/**
 * When invoke fails, log the initial error and retry other invokers (retry n times, which means at most n different invokers will be invoked)
 * Note that retry causes latency.
 * <p>
 * <a href="http://en.wikipedia.org/wiki/Failover">Failover</a>
 *
 */
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 {
        List<Invoker<T>> copyinvokers = invokers;
        checkInvokers(copyinvokers, invocation);

         // 獲取重試次數
        int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.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);

                 // 對 copyinvokers 進行判空檢查
                // 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 " + 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) {
                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 != 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);
    }

}

做了幾件事

1.獲取重試次數

2.根據重試次數進行迴圈呼叫,失敗後自動重試

3.呼叫前呼叫select方法通過負載均衡LoadBalance元件從一組符合路由規則的Invoker中選定一個Invoker

4.呼叫該選定的Invoker.invoke方法進行遠端呼叫

5.重試時會再次呼叫父類的 list 方法列舉 Invoker

 2.3 select方法 

這裡是呼叫的父類AbstractClusterInvoker.select

    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 配置,sticky 表示粘滯連線。所謂粘滯連線是指讓服務消費者儘可能的
    // 呼叫同一個服務提供者,除非該提供者掛了再進行切換
        boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY);
        {

             // 檢測 invokers 列表是否包含 stickyInvoker,如果不包含,
        // 說明 stickyInvoker 代表的服務提供者掛了,此時需要將其置空
            //ignore overloaded method
            if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
                stickyInvoker = null;
            }

            // 在 sticky 為 true,且 stickyInvoker != null 的情況下。如果 selected 包含 
        // stickyInvoker,表明 stickyInvoker 對應的服務提供者可能因網路原因未能成功提供服務。
        // 但是該提供者並沒掛,此時 invokers 列表中仍存在該服務提供者對應的 Invoker。
            //ignore concurrency problem
            if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {

                 // availablecheck 表示是否開啟了可用性檢查,如果開啟了,則呼叫 stickyInvoker 的 
            // isAvailable 方法進行檢查,如果檢查通過,則直接返回 stickyInvoker。
                if (availablecheck && stickyInvoker.isAvailable()) {
                    return stickyInvoker;
                }
            }
        }

        // 如果執行緒走到當前程式碼處,說明前面的 stickyInvoker 為空,或者不可用。
    // 此時繼續呼叫 doSelect 選擇 Invoker
        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);


         // 如果 sticky 為 true,則將負載均衡元件選出的 Invoker 賦值給 stickyInvoker
        if (sticky) {
            stickyInvoker = invoker;
        }
        return invoker;
    }

select 方法的主要邏輯集中在了對粘滯連線特性的支援上 

做了幾件事

1.首先是獲取 sticky 配置,然後再檢測 invokers 列表中是否包含 stickyInvoker。

2.如果不包含,則認為該 stickyInvoker 不可用,此時將其置空。

這裡的 invokers 列表是存活著的、且符合路由條件的服務提供者列表,如果這個列表不包含 stickyInvoker,那自然而然的認為 stickyInvoker 掛了,所以置空。

3.如果 stickyInvoker 存在於 invokers 列表中,此時要進行下一項檢測 — 檢測 selected 中是否包含 stickyInvoker。

這裡的selected是什麼,舉個例子:假設該次呼叫可重試5次,每次呼叫,會把呼叫過的invoker放到selected中。

如果包含的話,說明 stickyInvoker 在此之前沒有成功提供服務(但其仍然處於存活狀態)。

此時認為這個服務不可靠,不應該在重試期間內再次被呼叫,因此這個時候不會返回該 stick

 2.4 doSelect方法 

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 為空,這裡通過 SPI 載入 Loadbalance,預設為 RandomLoadBalance
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
        }

        // 通過負載均衡元件選擇 Invoker
        Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);


        // 如果 selected 包含負載均衡選擇出的 Invoker,或者該 Invoker 無法經過可用性檢查,此時進行重選
        //If the `invoker` is in the  `selected` or invoker is unavailable && availablecheck is true, reselect.
        if ((selected != null && selected.contains(invoker))
                || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
            try {

                 // 進行重選
                Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
                if (rinvoker != null) {

                     // 如果 rinvoker 不為空,則將其賦值給 invoker
                    invoker = rinvoker;
                } else {

                    // rinvoker 為空,定位 invoker 在 invokers 中的位置
                    //Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
                    int index = invokers.indexOf(invoker);
                    try {

                    // 獲取 index + 1 位置處的 Invoker,以下程式碼等價於:
                    //     invoker = invokers.get((index + 1) % invokers.size());
                        //Avoid collision
                        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;
    }

主要做了幾件事

1.通過負載均衡元件選擇 Invoker。

2.如果選出來的 Invoker 不穩定,或不可用,此時需要呼叫 reselect 方法進行重選。

 2.5 reselect 方法 

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

        //Allocating one in advance, this list is certain to be used.
        List<Invoker<T>> reselectInvokers = new ArrayList<Invoker<T>>(invokers.size() > 1 ? (invokers.size() - 1) : invokers.size());


         // 下面的 if-else 分支邏輯有些冗餘,pull request #2826 對這段程式碼進行了簡化,可以參考一下
    // 根據 availablecheck 進行不同的處理
        //First, try picking a invoker not in `selected`.
        if (availablecheck) { // invoker.isAvailable() should be checked

            // 遍歷 invokers 列表
            for (Invoker<T> invoker : invokers) {

                // 檢測可用性
                if (invoker.isAvailable()) {

                    // 如果 selected 列表不包含當前 invoker,則將其新增到 reselectInvokers 中
                    if (selected == null || !selected.contains(invoker)) {
                        reselectInvokers.add(invoker);
                    }
                }
            }

             // reselectInvokers 不為空,此時通過負載均衡元件進行選擇
            if (!reselectInvokers.isEmpty()) {
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }

        // 不檢查 Invoker 可用性
        } else { // do not check invoker.isAvailable()
            for (Invoker<T> invoker : invokers) {

                // 如果 selected 列表不包含當前 invoker,則將其新增到 reselectInvokers 中
                if (selected == null || !selected.contains(invoker)) {
                    reselectInvokers.add(invoker);
                }
            }
            if (!reselectInvokers.isEmpty()) {

                // 通過負載均衡元件進行選擇
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }
        }
        // Just pick an available invoker using loadbalance policy
        {

            // 若執行緒走到此處,說明 reselectInvokers 集合為空,此時不會呼叫負載均衡元件進行篩選。
        // 這裡從 selected 列表中查詢可用的 Invoker,並將其新增到 reselectInvokers 集合中
            if (selected != null) {
                for (Invoker<T> invoker : selected) {
                    if ((invoker.isAvailable()) // available first
                            && !reselectInvokers.contains(invoker)) {
                        reselectInvokers.add(invoker);
                    }
                }
            }
            if (!reselectInvokers.isEmpty()) {

                 // 再次進行選擇,並返回選擇結果
                return loadbalance.select(reselectInvokers, getUrl(), invocation);
            }
        }
        return null;
    }

 做了幾件事:

1.查詢可用的 Invoker,並將其新增到 reselectInvokers 集合中。

reselect先從 invokers 列表中查詢有效可用的 Invoker,若未能找到,此時再到 selected 列表中繼續查詢。

2.如果 reselectInvokers 不為空,則通過負載均衡元件再次進行選擇。

 3.FailbackClusterInvoker

FailbackClusterInvoker 會在呼叫失敗後,返回一個空結果給服務消費者。並通過定時任務對失敗的呼叫進行重傳,適合執行訊息通知等操作。 

/**
 * When fails, record failure requests and schedule for retry on a regular interval.
 * Especially useful for services of notification.
 *
 * <a href="http://en.wikipedia.org/wiki/Failback">Failback</a>
 *
 */
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) {

                    // 建立定時任務,每隔5秒執行一次
                    retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {

                        @Override
                        public void run() {
                            // collect retry statistics
                            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);
                }
            }
        }

         // 新增 invocation 和 invoker 到 failed 中
        failed.put(invocation, router);
    }

    void retryFailed() {
        if (failed.size() == 0) {
            return;
        }

        // 遍歷 failed,對失敗的呼叫進行重試
        for (Map.Entry<Invocation, AbstractClusterInvoker<?>> entry : new HashMap<Invocation, AbstractClusterInvoker<?>>(
                failed).entrySet()) {
            Invocation invocation = entry.getKey();
            Invoker<?> invoker = entry.getValue();
            try {

                // 再次進行呼叫
                invoker.invoke(invocation);

                // 呼叫成功後,從 failed 中移除 invoker
                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 {
            checkInvokers(invokers, invocation);

            // 選擇 Invoker
            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);

             // 進行呼叫
            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
        }
    }

}

 主要做了幾件事:

1. doInvoker方法負責初次的遠端呼叫。

若遠端呼叫失敗,則通過 addFailed 方法將呼叫資訊存入到 failed 中,等待定時重試。

2.addFailed方法在開始階段會根據 retryFuture 為空與否,來決定是否開啟定時任務。

3.retryFailed方法則是包含了失敗重試的邏輯,該方法會對 failed 進行遍歷,然後依次對 Invoker 進行呼叫。

呼叫成功則將 Invoker 從 failed 中移除,呼叫失敗則忽略失敗原因。

4.FailfastClusterInvoker

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 {
        checkInvokers(invokers, invocation);

        // 選擇 Invoker
        Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
        try {

            // 呼叫 Invoker
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception.

                // 丟擲異常
                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);
        }
    }
}

主要做了1件事:

 通過 select 方法選擇 Invoker,然後進行遠端呼叫。如果呼叫失敗,則立即丟擲異常

5.FailsafeClusterInvoker

FailsafeClusterInvoker 是一種失敗安全的 Cluster Invoker。所謂的失敗安全是指,當呼叫過程中出現異常時,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<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(); // ignore
        }
    }
}

 6.ForkingClusterInvoker

ForkingClusterInvoker 會在執行時通過執行緒池建立多個執行緒,併發呼叫多個服務提供者。

只要有一個服務提供者成功返回了結果,doInvoke 方法就會立即結束執行。

ForkingClusterInvoker 的應用場景是在一些對實時性要求比較高讀操作(注意是讀操作,並行寫操作可能不安全)下使用,但這將會耗費更多的資源。 

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
    @SuppressWarnings({"unchecked", "rawtypes"})
    public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            checkInvokers(invokers, invocation);
            final List<Invoker<T>> selected;

            // 獲取 forks 配置
            final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS);

            // 獲取超時配置
            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>>();

                 // 迴圈選出 forks 個 Invoker,並新增到 selected 中
                for (int i = 0; i < forks; i++) {

                    // 選擇 Invoker
                    // TODO. Add some comment here, refer chinese version for more details.
                    Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
                    if (!selected.contains(invoker)) {//Avoid add the same invoker several times.
                        selected.add(invoker);
                    }
                }
            }
            RpcContext.getContext().setInvokers((List) selected);
            final AtomicInteger count = new AtomicInteger();
            final BlockingQueue<Object> ref = new LinkedBlockingQueue<Object>();

            // 遍歷 selected 列表
            for (final Invoker<T> invoker : selected) {

                 // 為每個 Invoker 建立一個執行執行緒
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {

                            // 進行遠端呼叫
                            Result result = invoker.invoke(invocation);

                            // 將結果存到阻塞佇列中
                            ref.offer(result);
                        } catch (Throwable e) {
                            int value = count.incrementAndGet();

                            // 僅在 value 大於等於 selected.size() 時,才將異常物件
                            // 放入阻塞佇列中,請大家思考一下為什麼要這樣做。
                            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 {
            // clear attachments which is binding to current thread.
            RpcContext.getContext().clearAttachments();
        }
    }
}

主要做了幾件事:

1.選出 forks 個 Invoker,為接下來的併發呼叫提供輸入。

2.通過執行緒池併發呼叫多個 Invoker,並將結果儲存在阻塞佇列中。

這裡有個問題:

為什麼要在value >= selected.size()的情況下,才將異常物件新增到阻塞佇列中?

原因是:

在並行呼叫多個服務提供者的情況下,只要有一個服務提供者能夠成功返回結果,而其他全部失敗。

此時 ForkingClusterInvoker 仍應該返回成功的結果,而非丟擲異常。

value >= selected.size()時將異常物件放入阻塞佇列中,可以保證異常物件不會出現在正常結果的前面,這樣可從阻塞佇列中優先取出正常的結果。

3.從阻塞佇列中獲取返回結果,並對返回結果型別進行判斷。如果為異常型別,則直接丟擲,否則返回。

7.BroadcastClusterInvoker

BroadcastClusterInvoker 會逐個呼叫每個服務提供者,如果其中一臺報錯,在迴圈呼叫結束後,BroadcastClusterInvoker 會丟擲異常。該類通常用於通知所有提供者更新快取或日誌等本地資源資訊。 

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;

        // 遍歷 Invoker 列表,逐個呼叫
        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);
            }
        }

        // exception 不為空,則丟擲異常
        if (exception != null) {
            throw exception;
        }
        return result;
    }

}

 

相關文章