dubbo 服務(wù)路由

目錄
dubbo 拓展機(jī)制 SPI
dubbo 自適應(yīng)拓展機(jī)制
dubbo 服務(wù)導(dǎo)出
dubbo 服務(wù)引用
dubbo 服務(wù)字典
dubbo 服務(wù)路由
dubbo 集群
dubbo 負(fù)載均衡
dubbo 服務(wù)調(diào)用過程

上一篇文章分析了集群容錯(cuò)的第一部分 — 服務(wù)目錄 Directory齿拂。服務(wù)目錄在刷新 Invoker 列表的過程中,會(huì)通過 Router 進(jìn)行服務(wù)路由轩勘,篩選出符合路由規(guī)則的服務(wù)提供者。在詳細(xì)分析服務(wù)路由的源碼之前逞频,先來介紹一下服務(wù)路由是什么纺念。服務(wù)路由包含一條路由規(guī)則,路由規(guī)則決定了服務(wù)消費(fèi)者的調(diào)用目標(biāo)州邢,即規(guī)定了服務(wù)消費(fèi)者可調(diào)用哪些服務(wù)提供者杉武。Dubbo 目前提供了三種服務(wù)路由實(shí)現(xiàn)辙诞,分別為條件路由 ConditionRouter、腳本路由 ScriptRouter 和標(biāo)簽路由 TagRouter轻抱。其中條件路由是我們最常使用的飞涂,標(biāo)簽路由是一個(gè)新的實(shí)現(xiàn),暫時(shí)還未發(fā)布祈搜,該實(shí)現(xiàn)預(yù)計(jì)會(huì)在 2.7.x 版本中發(fā)布较店。本篇文章將分析條件路由相關(guān)源碼,腳本路由和標(biāo)簽路由這里就不分析了容燕。

1表達(dá)式解析

條件路由規(guī)則是一條字符串泽西,對(duì)于 Dubbo 來說,它并不能直接理解字符串的意思缰趋,需要將其解析成內(nèi)部格式才行。條件表達(dá)式的解析過程始于 ConditionRouter 的構(gòu)造方法陕见,下面一起看一下

public ConditionRouter(URL url) {
    this.url = url;
    // 獲取 priority秘血,路由規(guī)則的優(yōu)先級(jí),用于排序评甜,優(yōu)先級(jí)越大越靠前執(zhí)行
    this.priority = url.getParameter(PRIORITY_KEY, 0);
    // false灰粮,不調(diào)用mock服務(wù)。
    // true忍坷,當(dāng)服務(wù)調(diào)用失敗時(shí)粘舟,使用mock服務(wù)熔脂。
    // default,當(dāng)服務(wù)調(diào)用失敗時(shí)柑肴,使用mock服務(wù)霞揉。
    // force,強(qiáng)制使用Mock服務(wù)(不管服務(wù)能否調(diào)用成功)晰骑。(使用xml配置不生效,使用ReferenceConfigAPI可以生效)
    // mock功能的一種模式
    this.force = url.getParameter(FORCE_KEY, false);
    this.enabled = url.getParameter(ENABLED_KEY, true);
    // 解析rule字符串
    init(url.getParameterAndDecoded(RULE_KEY));
}

public void init(String rule) {
    try {
        if (rule == null || rule.trim().length() == 0) {
            throw new IllegalArgumentException("Illegal route rule!");
        }
        rule = rule.replace("consumer.", "").replace("provider.", "");
        // 定位 => 分隔符
        int i = rule.indexOf("=>");
        // 分別獲取服務(wù)消費(fèi)者和提供者匹配規(guī)則
        String whenRule = i < 0 ? null : rule.substring(0, i).trim();
        String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
        // 解析服務(wù)消費(fèi)者匹配規(guī)則
        Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
        // 解析服務(wù)提供者匹配規(guī)則
        Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
        // NOTE: It should be determined on the business level whether the `When condition` can be empty or not.
        this.whenCondition = when;
        this.thenCondition = then;
    } catch (ParseException e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
}

在繼續(xù)看parseRule方法前适秩,先看一個(gè)內(nèi)部類

protected static final class MatchPair {
        // 存放匹配條件
        final Set<String> matches = new HashSet<String>();
        // 存放不匹配的條件
        final Set<String> mismatches = new HashSet<String>();
}

繼續(xù)看parseRule方法

private static Map<String, MatchPair> parseRule(String rule)
            throws ParseException {
        // 定義條件映射集合
        Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
        if (StringUtils.isBlank(rule)) {
            return condition;
        }
        // Key-Value pair, stores both match and mismatch conditions
        MatchPair pair = null;
        // Multiple values
        Set<String> values = null;
        // 通過正則表達(dá)式匹配路由規(guī)則,ROUTE_PATTERN = ([&!=,]*)\s*([^&!=,\s]+)
        // 這個(gè)表達(dá)式看起來不是很好理解硕舆,第一個(gè)括號(hào)內(nèi)的表達(dá)式用于匹配"&", "!", "=" 和 "," 等符號(hào)秽荞。
        // 第二括號(hào)內(nèi)的用于匹配英文字母,數(shù)字等字符抚官。舉個(gè)例子說明一下:
        //    host = 2.2.2.2 & host != 1.1.1.1 & method = hello
        // 匹配結(jié)果如下:
        //     括號(hào)一      括號(hào)二
        // 1.  null       host
        // 2.   =         2.2.2.2
        // 3.   &         host
        // 4.   !=        1.1.1.1
        // 5.   &         method
        // 6.   =         hello
        final Matcher matcher = ROUTE_PATTERN.matcher(rule);
        while (matcher.find()) { // Try to match one by one
            // 獲取括號(hào)一內(nèi)的匹配結(jié)果
            String separator = matcher.group(1);
            // 獲取括號(hào)二內(nèi)的匹配結(jié)果
            String content = matcher.group(2);
            // Start part of the condition expression.
            // 分隔符為空扬跋,表示匹配的是表達(dá)式的開始部分
            if (StringUtils.isEmpty(separator)) {
                pair = new MatchPair();
                condition.put(content, pair);
            }
            // The KV part of the condition expression
            // 如果分隔符為 &,表明接下來也是一個(gè)條件
            else if ("&".equals(separator)) {
                if (condition.get(content) == null) {
                    pair = new MatchPair();
                    condition.put(content, pair);
                } else {
                    pair = condition.get(content);
                }
            }
            // The Value in the KV part.
            // 分隔符為 =
            else if ("=".equals(separator)) {
                if (pair == null) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }

                values = pair.matches;
                values.add(content);
            }
            // The Value in the KV part.
            //  分隔符為 !=
            else if ("!=".equals(separator)) {
                if (pair == null) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }

                values = pair.mismatches;
                values.add(content);
            }
            // The Value in the KV part, if Value have more than one items.
            // 分隔符為 ,
            else if (",".equals(separator)) { // Should be separated by ','
                if (values == null || values.isEmpty()) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }
                // 將 content 存入到上一步獲取到的 values 中凌节,可能是 matches钦听,也可能是 mismatches
                values.add(content);
            } else {
                throw new ParseException("Illegal route rule \"" + rule
                        + "\", The error char '" + separator + "' at index "
                        + matcher.start() + " before \"" + content + "\".", matcher.start());
            }
        }
        return condition;
    }

如:host = 2.2.2.2 & host != 1.1.1.1 & method = hello將被解析為


image.png

2.服務(wù)路由

服務(wù)路由的入口方法是 ConditionRouter 的 router 方法,該方法定義在 Router 接口中刊咳。實(shí)現(xiàn)代碼如下:

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
        throws RpcException {
    if (!enabled) {
        return invokers;
    }

    if (CollectionUtils.isEmpty(invokers)) {
        return invokers;
    }
    try {
        // 先對(duì)服務(wù)消費(fèi)者條件進(jìn)行匹配彪见,如果匹配失敗,表明服務(wù)消費(fèi)者 url 不符合匹配規(guī)則娱挨,
        // 無需進(jìn)行后續(xù)匹配余指,直接返回 Invoker 列表即可。比如下面的規(guī)則:
        //     host = 10.20.153.10 => host = 10.0.0.10
        // 這條路由規(guī)則希望 IP 為 10.20.153.10 的服務(wù)消費(fèi)者調(diào)用 IP 為 10.0.0.10 機(jī)器上的服務(wù)跷坝。
        // 當(dāng)消費(fèi)者 ip 為 10.20.153.11 時(shí)酵镜,matchWhen 返回 false,表明當(dāng)前這條路由規(guī)則不適用于
        // 當(dāng)前的服務(wù)消費(fèi)者柴钻,此時(shí)無需再進(jìn)行后續(xù)匹配淮韭,直接返回即可。
        if (!matchWhen(url, invocation)) {
            return invokers;
        }
        List<Invoker<T>> result = new ArrayList<Invoker<T>>();
        // 服務(wù)提供者匹配條件未配置贴届,表明對(duì)指定的服務(wù)消費(fèi)者禁用服務(wù)靠粪,也就是服務(wù)消費(fèi)者在黑名單中
        if (thenCondition == null) {
            logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
            return result;
        }
        // 這里可以簡單的把 Invoker 理解為服務(wù)提供者,現(xiàn)在使用服務(wù)提供者匹配規(guī)則對(duì)
        // Invoker 列表進(jìn)行匹配
        for (Invoker<T> invoker : invokers) {
            if (matchThen(invoker.getUrl(), url)) {
                result.add(invoker);
            }
        }
        // 返回匹配結(jié)果毫蚓,如果 result 為空列表占键,且 force = true,表示強(qiáng)制返回空列表元潘,
        // 否則路由結(jié)果為空的路由規(guī)則將自動(dòng)失效
        if (!result.isEmpty()) {
            return result;
        } else if (force) {
            logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
            return result;
        }
    } catch (Throwable t) {
        logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
    }
    return invokers;
}

router 方法先是調(diào)用 matchWhen 對(duì)服務(wù)消費(fèi)者進(jìn)行匹配畔乙,如果匹配失敗,直接返回 Invoker 列表翩概。如果匹配成功牲距,再對(duì)服務(wù)提供者進(jìn)行匹配返咱,匹配邏輯封裝在了 matchThen 方法中。下面來看一下這兩個(gè)方法的邏輯:

boolean matchWhen(URL url, Invocation invocation) {
        // 服務(wù)消費(fèi)者條件為 null 或空牍鞠,均返回 true咖摹,比如:
        //     => host != 172.22.3.91
        // 表示所有的服務(wù)消費(fèi)者都不得調(diào)用 IP 為 172.22.3.91 的機(jī)器上的服務(wù)
        return CollectionUtils.isEmptyMap(whenCondition) || matchCondition(whenCondition, url, null, invocation);
    }

    private boolean matchThen(URL url, URL param) {
        // 服務(wù)提供者條件為 null 或空,表示禁用服務(wù)
        return CollectionUtils.isNotEmptyMap(thenCondition) && matchCondition(thenCondition, url, param, null);
    }

繼續(xù)看matchCondition

private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {
        // 將服務(wù)提供者或消費(fèi)者 url 轉(zhuǎn)成 Map
        Map<String, String> sample = url.toMap();
        boolean result = false;
        // 遍歷 condition 列表
        for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {
            // 獲取匹配項(xiàng)名稱皮服,比如 host楞艾、method 等
            String key = matchPair.getKey();
            String sampleValue;
            //get real invoked method name from invocation
            // 如果 invocation 不為空,且 key 為 mehtod(s)龄广,表示進(jìn)行方法匹配
            if (invocation != null && (METHOD_KEY.equals(key) || METHODS_KEY.equals(key))) {
                // 從 invocation 獲取被調(diào)用方法的名稱
                sampleValue = invocation.getMethodName();
            } else if (ADDRESS_KEY.equals(key)) {
                // 獲取服務(wù)提供者或是服務(wù)消費(fèi)者的地址
                sampleValue = url.getAddress();
            } else if (HOST_KEY.equals(key)) {
                // 獲取服務(wù)提供者或是服務(wù)消費(fèi)者的host
                sampleValue = url.getHost();
            } else {
                // 從服務(wù)提供者或消費(fèi)者 url 中獲取指定字段值硫眯,比如 application 等
                sampleValue = sample.get(key);
                if (sampleValue == null) {
                    // 嘗試通過 default.xxx 獲取相應(yīng)的值
                    sampleValue = sample.get(DEFAULT_KEY_PREFIX + key);
                }
            }
            if (sampleValue != null) {
                // 調(diào)用 MatchPair 的 isMatch 方法進(jìn)行匹配
                if (!matchPair.getValue().isMatch(sampleValue, param)) {
                    // 只要有一個(gè)規(guī)則匹配失敗,立即返回 false 結(jié)束方法邏輯
                    return false;
                } else {
                    result = true;
                }
            } else {
                //not pass the condition
                // sampleValue 為空择同,表明服務(wù)提供者或消費(fèi)者 url 中不包含相關(guān)字段两入。此時(shí)如果
                // MatchPair 的 matches 不為空,表示匹配失敗敲才,返回 false裹纳。比如我們有這樣
                // 一條匹配條件 loadbalance = random,假設(shè) url 中并不包含 loadbalance 參數(shù)紧武,
                // 此時(shí) sampleValue = null剃氧。既然路由規(guī)則里限制了 loadbalance 必須為 random,
                // 但 sampleValue = null阻星,明顯不符合規(guī)則朋鞍,因此返回 false
                if (!matchPair.getValue().matches.isEmpty()) {
                    return false;
                } else {
                    result = true;
                }
            }
        }
        return result;
    }

主要的匹配規(guī)則都在isMatch中

private boolean isMatch(String value, URL param) {
            // 情況一:matches 非空,mismatches 為空
            if (!matches.isEmpty() && mismatches.isEmpty()) {
                for (String match : matches) {
                    // 遍歷 matches 集合妥箕,檢測(cè)入?yún)?value 是否能被 matches 集合元素匹配到滥酥。
                    // 舉個(gè)例子,如果 value = 10.20.153.11畦幢,matches = [10.20.153.*],
                    // 此時(shí) isMatchGlobPattern 方法返回 true
                    if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                        return true;
                    }
                }
                // 如果所有匹配項(xiàng)都無法匹配到入?yún)⒖参牵瑒t返回 false
                return false;
            }

            // 情況二:matches 為空,mismatches 非空
            if (!mismatches.isEmpty() && matches.isEmpty()) {
                for (String mismatch : mismatches) {
                    // 只要入?yún)⒈?mismatches 集合中的任意一個(gè)元素匹配到宇葱,就返回 false
                    if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                        return false;
                    }
                }
                return true;
            }

            // 情況三:matches 非空瘦真,mismatches 非空
            if (!matches.isEmpty() && !mismatches.isEmpty()) {
                //when both mismatches and matches contain the same value, then using mismatches first
                // matches 和 mismatches 均為非空,此時(shí)優(yōu)先使用 mismatches 集合元素對(duì)入?yún)⑦M(jìn)行匹配黍瞧。
                // 只要 mismatches 集合中任意一個(gè)元素與入?yún)⑵ヅ涑晒β鹗希土⒓捶祷?false,結(jié)束方法邏輯
                for (String mismatch : mismatches) {
                    if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                        return false;
                    }
                }
                // mismatches 集合元素?zé)o法匹配到入?yún)⒗啄妫藭r(shí)再使用 matches 繼續(xù)匹配
                for (String match : matches) {
                    // 只要 matches 集合中任意一個(gè)元素與入?yún)⑵ヅ涑晒Γ土⒓捶祷?true
                    if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                        return true;
                    }
                }
                // 全部失配污尉,則返回 false
                return false;
            }
            // 情況四:matches 和 mismatches 均為空膀哲,此時(shí)返回 false
            return false;
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末往产,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子某宪,更是在濱河造成了極大的恐慌仿村,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兴喂,死亡現(xiàn)場(chǎng)離奇詭異蔼囊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)衣迷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門畏鼓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人壶谒,你說我怎么就攤上這事云矫。” “怎么了汗菜?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵让禀,是天一觀的道長。 經(jīng)常有香客問我陨界,道長巡揍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任菌瘪,我火速辦了婚禮腮敌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘麻车。我一直安慰自己缀皱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布动猬。 她就那樣靜靜地躺著啤斗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赁咙。 梳的紋絲不亂的頭發(fā)上钮莲,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音彼水,去河邊找鬼崔拥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凤覆,可吹牛的內(nèi)容都是我干的链瓦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼慈俯!你這毒婦竟也來了渤刃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤贴膘,失蹤者是張志新(化名)和其女友劉穎卖子,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刑峡,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洋闽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了突梦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诫舅。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖阳似,靈堂內(nèi)的尸體忽然破棺而出骚勘,到底是詐尸還是另有隱情,我是刑警寧澤撮奏,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布俏讹,位于F島的核電站,受9級(jí)特大地震影響畜吊,放射性物質(zhì)發(fā)生泄漏泽疆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一玲献、第九天 我趴在偏房一處隱蔽的房頂上張望殉疼。 院中可真熱鬧,春花似錦捌年、人聲如沸瓢娜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眠砾。三九已至,卻和暖如春托酸,著一層夾襖步出監(jiān)牢的瞬間褒颈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工励堡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谷丸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓应结,卻偏偏與公主長得像刨疼,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. 簡介 上一篇文章分析了集群容錯(cuò)的第一部分 – 服務(wù)目錄 Directory揩慕。服務(wù)目錄在刷新 Invoker ...
    Java大生閱讀 523評(píng)論 0 0
  • 先看官網(wǎng)兩張圖【引用來自官網(wǎng)】:image.png 官網(wǎng)說明: 1.首先 ReferenceConfig 類的 i...
    致慮閱讀 1,027評(píng)論 0 2
  • 生活中哪些是真實(shí)的游两,而哪些又是虛假的。我是活在真實(shí)的世界里嗎漩绵?卻猶如是一場(chǎng)夢(mèng),我是活在夢(mèng)里嗎肛炮?卻又似乎是置身于一個(gè)...
    泡泡魚dairying閱讀 103評(píng)論 0 1
  • 1 不得不承認(rèn)止吐,韓靖逸是我們那一百來號(hào)人輔修班里長得最漂亮的。我敢保證第一年輔修課我壓根記不住這號(hào)人侨糟,我們永遠(yuǎn)是一...
    Hyuan閱讀 491評(píng)論 0 1
  • 不忘初心碍扔,方能始終。初心易得秕重,始終難守不同!前者不管生活中遇到任何困難或麻煩我們都要盡量去克服,戰(zhàn)勝自己溶耘,永遠(yuǎn)不忘那份...
    遠(yuǎn)佳閱讀 256評(píng)論 1 2