Dubbo--集群調(diào)用與容錯(cuò)

集群調(diào)用

為了提供性能與保持高可用屿岂,通常一個(gè)接口能力會(huì)有多個(gè)提供者,也就是集群(Cluster)爷怀。

下面分析一下,Dubbo中一次invoke(調(diào)用)的流程磺浙。

首先介紹一下幾個(gè)關(guān)鍵類徒坡,可以結(jié)合具體流程在進(jìn)行消化瘤缩。

cluster

各節(jié)點(diǎn)關(guān)系(參考Dubbo官網(wǎng))

  • ClusterDirectory 中的多個(gè) Invoker 偽裝成一個(gè) Invoker,對(duì)上層透明锦溪,偽裝過程包含了容錯(cuò)邏輯府怯,調(diào)用失敗后牺丙,重試另一個(gè)
  • InvokerProvider 的一個(gè)可調(diào)用 Service 的抽象,Invoker 封裝了 Provider 地址及 Service 接口信息亿昏。
image
  • Directory 代表多個(gè) Invoker角钩,可以把它看成 List<Invoker> 呻澜,但與 List 不同的是羹幸,它的值可能是動(dòng)態(tài)變化的,比如注冊(cè)中心推送變更
  • Router 負(fù)責(zé)從多個(gè) Invoker 中按路由規(guī)則選出子集供炼,比如讀寫分離窘疮,應(yīng)用隔離等
  • LoadBalance 負(fù)責(zé)從多個(gè) Invoker 中選出具體的一個(gè)用于本次調(diào)用袋哼,選的過程包含了負(fù)載均衡算法,調(diào)用失敗后闸衫,需要重選

invoke流程

使用的模板方法設(shè)計(jì)模式,可以看到一些主流程都在父類的AbstractClusterInvoker中蔚出,一些特殊實(shí)現(xiàn)如doXxx(比如doList、doInvoke)根據(jù)配置的不同子類骄酗,或者根據(jù)SPI定制化實(shí)現(xiàn)稀余。

AbstractClusterInvoker#invoke

public Result invoke(final Invocation invocation) throws RpcException {
    //校驗(yàn)服務(wù)可用性,是否已經(jīng)被摧毀
    checkWhetherDestroyed();

    //將一些上下文信息綁定到invocation中睛琳,可以傳遞到遠(yuǎn)程服務(wù)器
    Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
    if (contextAttachments != null && contextAttachments.size() != 0) {
        ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
    }
    //將可用的invokers全部列出來(DynamicDirectory、RegistryDirectory)踏烙,再根據(jù)router規(guī)則進(jìn)行過濾選出子集师骗,如讀寫分離等
    List<Invoker<T>> invokers = list(invocation);
    //進(jìn)行負(fù)載均衡,選擇真正調(diào)用的一個(gè)提供者讨惩,默認(rèn)使用random
    LoadBalance loadbalance = initLoadBalance(invokers, invocation);
    //若是異步調(diào)用則記錄調(diào)用id辟癌,為了冪等
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    //根據(jù)集群容錯(cuò)策略進(jìn)行實(shí)際調(diào)用根據(jù)荐捻,默認(rèn)failover
    return doInvoke(invocation, invokers, loadbalance);
}

可以看到很多使用默認(rèn)值黍少,默認(rèn)實(shí)現(xiàn)是如何選擇的呢仍侥?可以看到是通過SPI機(jī)制指定缺省實(shí)現(xiàn)的

@SPI(Cluster.DEFAULT)
public interface Cluster {
    String DEFAULT = FailoverCluster.NAME;
    
}

public class FailoverCluster extends AbstractCluster {
    public final static String NAME = "failover";
}

集群容錯(cuò)

容錯(cuò)策略有很多,這里分析缺省實(shí)現(xiàn)Failover

Failover:當(dāng)調(diào)用失敗時(shí),記錄初始錯(cuò)誤并重試其他調(diào)用者(重試n次传于,這意味著最多將調(diào)用n個(gè)不同的調(diào)用者)請(qǐng)注意,重試會(huì)導(dǎo)致延遲沼溜。默認(rèn)延遲為5s系草。適合實(shí)時(shí)性不高的讀操作。

FailoverClusterInvoker#doInvoke也就是父類最后一步的具體調(diào)用實(shí)現(xiàn)唆涝。

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    List<Invoker<T>> copyInvokers = invokers;
    //校驗(yàn)invokers是否為空
    checkInvokers(copyInvokers, invocation);
    //獲取方法名
    String methodName = RpcUtils.getMethodName(invocation);
    //從url中獲取配置的重試次數(shù)并進(jìn)行+1找都,若配置重試次數(shù)為2,那么最多調(diào)用3次廊酣。
    int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
    //防御性編程能耻,若配置的為負(fù)數(shù)或0也會(huì)進(jìn)行一次調(diào)用
    if (len <= 0) {
        len = 1;
    }
    // retry loop.
    // last exception.
    RpcException le = null;
    //已經(jīng)調(diào)用過的會(huì)在后續(xù)負(fù)載均衡進(jìn)行過濾。
    List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); 
    //調(diào)用過的服務(wù)進(jìn)行地址記錄
    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) {
            //校驗(yàn)是否被銷毀
            checkWhetherDestroyed();
            //列出提供者
            copyInvokers = list(invocation);
            // check again是否為空
            checkInvokers(copyInvokers, invocation);
        }
        //進(jìn)行負(fù)載均衡選擇亡驰,如果選擇出來invoker已經(jīng)被調(diào)用過則reselect一把
        Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
        //將調(diào)用過的放入list晓猛,日志記錄使用
        invoked.add(invoker);
        RpcContext.getContext().setInvokers((List) invoked);
        try {
            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) {
            //業(yè)務(wù)異常則拋出
            if (e.isBiz()) { 
                throw e;
            }
            le = e;
        } catch (Throwable e) {
            //網(wǎng)絡(luò)等異常則進(jìn)行重試并記錄上一次的異常
            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);
}

整體流程

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凡辱,隨后出現(xiàn)的幾起案子戒职,更是在濱河造成了極大的恐慌,老刑警劉巖透乾,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洪燥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡续徽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門亲澡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钦扭,“玉大人,你說我怎么就攤上這事床绪】颓椋” “怎么了其弊?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)膀斋。 經(jīng)常有香客問我梭伐,道長(zhǎng),這世上最難降的妖魔是什么仰担? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任糊识,我火速辦了婚禮,結(jié)果婚禮上摔蓝,老公的妹妹穿的比我還像新娘赂苗。我一直安慰自己,他們只是感情好贮尉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布拌滋。 她就那樣靜靜地躺著,像睡著了一般猜谚。 火紅的嫁衣襯著肌膚如雪败砂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天魏铅,我揣著相機(jī)與錄音昌犹,去河邊找鬼。 笑死沦零,一個(gè)胖子當(dāng)著我的面吹牛祭隔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播路操,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疾渴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了屯仗?” 一聲冷哼從身側(cè)響起搞坝,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎魁袜,沒想到半個(gè)月后桩撮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡峰弹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年店量,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞠呈。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡融师,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚁吝,到底是詐尸還是另有隱情旱爆,我是刑警寧澤舀射,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站怀伦,受9級(jí)特大地震影響脆烟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜房待,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一邢羔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吴攒,春花似錦张抄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至镣隶,卻和暖如春极谊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背安岂。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工轻猖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人域那。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓咙边,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親次员。 傳聞我的和親對(duì)象是個(gè)殘疾皇子败许,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354