Dubbo 源碼分析 - 集群容錯之 Router

1. 簡介

上一篇文章分析了集群容錯的第一部分 – 服務(wù)目錄 Directory牍白。服務(wù)目錄在刷新 Invoker 列表的過程中溜族,會通過 Router 進行服務(wù)路由买优。上一篇文章關(guān)于服務(wù)路由相關(guān)邏輯沒有細(xì)致分析末捣,一筆帶過了进每,本篇文章將對此進行詳細(xì)的分析。首先射亏,先來介紹一下服務(wù)目錄是什么近忙。服務(wù)路由包含一條路由規(guī)則竭业,路由規(guī)則決定了服務(wù)消費者的調(diào)用目標(biāo),即規(guī)定了服務(wù)消費者可調(diào)用哪些服務(wù)提供者及舍。Dubbo 目前提供了三種服務(wù)路由實現(xiàn)未辆,分別為條件路由 ConditionRouter、腳本路由 ScriptRouter 和標(biāo)簽路由 TagRouter锯玛。其中條件路由是我們最常使用的咐柜,標(biāo)簽路由暫未在我所分析的 2.6.4 版本中提供,該實現(xiàn)會在 2.7.0 版本中提供攘残。本篇文章將分析條件路由相關(guān)源碼拙友,腳本路由和標(biāo)簽路由這里就不分析了。下面進入正題歼郭。

2. 源碼分析

條件路由規(guī)則有兩個條件組成遗契,分別用于對服務(wù)消費者和提供者進行匹配。比如有這樣一條規(guī)則:

host = 10.20.153.10 => host = 10.20.153.11

該條規(guī)則表示 IP 為 10.20.153.10 的服務(wù)消費者只可調(diào)用 IP 為 10.20.153.11 機器上的服務(wù)实撒,不可調(diào)用其他機器上的服務(wù)姊途。條件路由規(guī)則的格式如下:

[服務(wù)消費者匹配條件] => [服務(wù)提供者匹配條件]

如果服務(wù)消費者匹配條件為空,表示不對服務(wù)消費者進行限制知态。如果服務(wù)提供者匹配條件為空捷兰,表示對某些服務(wù)消費者禁用服務(wù)。Dubbo 官方文檔對條件路由進行了比較詳細(xì)的介紹负敏,大家可以參考下贡茅,這里就不過多說明了。

條件路由實現(xiàn)類 ConditionRouter 需要對用戶配置的路由規(guī)則進行解析其做,得到一系列的條件顶考。然后再根據(jù)這些條件對服務(wù)進行路由。本章將分兩節(jié)進行說明妖泄,2.1節(jié)介紹表達(dá)式解析過程驹沿。2.2 節(jié)介紹服務(wù)路由的過程。接下來蹈胡,我們先從表達(dá)式解析過程看起渊季。

2.1 表達(dá)式解析

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

public ConditionRouter(URL url) {

? ? this.url = url;

? ? // 獲取 priority 和 force 配置

? ? this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);

? ? this.force = url.getParameter(Constants.FORCE_KEY, false);

? ? try {

? ? ? ? // 獲取路由規(guī)則

? ? ? ? String rule = url.getParameterAndDecoded(Constants.RULE_KEY);

? ? ? ? if (rule == null || rule.trim().length() == 0) {

? ? ? ? ? ? throw new IllegalArgumentException("Illegal route rule!");

? ? ? ? }

? ? ? ? rule = rule.replace("consumer.", "").replace("provider.", "");

? ? ? ? // 定位 => 分隔符

? ? ? ? int i = rule.indexOf("=>");

? ? ? ? // 分別獲取服務(wù)消費者和提供者匹配規(guī)則

? ? ? ? String whenRule = i < 0 ? null : rule.substring(0, i).trim();

? ? ? ? String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();

? ? ? ? // 解析服務(wù)消費者匹配規(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);

? ? ? ? this.whenCondition = when;

? ? ? ? this.thenCondition = then;

? ? } catch (ParseException e) {

? ? ? ? throw new IllegalStateException(e.getMessage(), e);

? ? }

}

如上合砂,ConditionRouter 構(gòu)造方法先是對路由規(guī)則做預(yù)處理,然后調(diào)用 parseRule 方法分別對服務(wù)提供者和消費者規(guī)則進行解析源织,最后將解析結(jié)果賦值給 whenCondition 和 thenCondition 成員變量翩伪。ConditionRouter 構(gòu)造方法不是很復(fù)雜微猖,這里就不多說了。下面我們把重點放在 parseRule 方法上幻工,在詳細(xì)介紹這個方法之前励两,我們先來看一個內(nèi)部類黎茎。

private static final class MatchPair {

? ? final Set<String> matches = new HashSet<String>();

? ? final Set<String> mismatches = new HashSet<String>();

}

MatchPair 內(nèi)部包含了兩個 Set 型的成員變量囊颅,分別用于存放匹配和不匹配的條件。這個類兩個成員變量會在 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;

? ? }

? ? MatchPair pair = null;

? ? Set<String> values = null;

? ? // 通過正則表達(dá)式匹配路由規(guī)則,ROUTE_PATTERN = ([&!=,]*)\s*([^&!=,\s]+)

? ? // 這個表達(dá)式看起來不是很好理解嗅骄,第一個括號內(nèi)的表達(dá)式用于匹配"&", "!", "=" 和 "," 等符號胳挎。

? ? // 第二括號內(nèi)的用于匹配英文字母,數(shù)字等字符溺森。舉個例子說明一下:

? ? //? ? host = 2.2.2.2 & host != 1.1.1.1 & method = hello

? ? // 匹配結(jié)果如下:

? ? //? ? 括號一? ? ? 括號二

? ? // 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()) {

? ? ? // 獲取括號一內(nèi)的匹配結(jié)果

? ? ? ? String separator = matcher.group(1);

? ? ? ? // 獲取括號二內(nèi)的匹配結(jié)果

? ? ? ? String content = matcher.group(2);

? ? ? ? // 分隔符為空慕爬,表示匹配的是表達(dá)式的開始部分

? ? ? ? if (separator == null || separator.length() == 0) {

? ? ? ? ? ? // 創(chuàng)建 MatchPair 對象

? ? ? ? ? ? pair = new MatchPair();

? ? ? ? ? ? // 存儲 <匹配項, MatchPair> 鍵值對,比如 <host, MatchPair>

? ? ? ? ? ? condition.put(content, pair);

? ? ? ? }


? ? ? ? // 如果分隔符為 &屏积,表明接下來也是一個條件

? ? ? ? else if ("&".equals(separator)) {

? ? ? ? ? ? // 嘗試從 condition 獲取 MatchPair

? ? ? ? ? ? if (condition.get(content) == null) {

? ? ? ? ? ? ? ? // 未獲取到 MatchPair医窿,重新創(chuàng)建一個,并放入 condition 中

? ? ? ? ? ? ? ? pair = new MatchPair();

? ? ? ? ? ? ? ? condition.put(content, pair);

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? pair = condition.get(content);

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? // 分隔符為 =

? ? ? ? else if ("=".equals(separator)) {

? ? ? ? ? ? if (pair == null)

? ? ? ? ? ? ? ? throw new ParseException("Illegal route rule ...");

? ? ? ? ? ? values = pair.matches;

? ? ? ? ? ? // 將 content 存入到 MatchPair 的 matches 集合中

? ? ? ? ? ? values.add(content);

? ? ? ? }


? ? ? ? //? 分隔符為 !=

? ? ? ? else if ("!=".equals(separator)) {

? ? ? ? ? ? if (pair == null)

? ? ? ? ? ? ? ? throw new ParseException("Illegal route rule ...");

? ? ? ? ? ? values = pair.mismatches;

? ? ? ? ? ? // 將 content 存入到 MatchPair 的 mismatches 集合中

? ? ? ? ? ? values.add(content);

? ? ? ? }


? ? ? ? // 分隔符為 ,

? ? ? ? else if (",".equals(separator)) {

? ? ? ? ? ? if (values == null || values.isEmpty())

? ? ? ? ? ? ? ? throw new ParseException("Illegal route rule ...");

? ? ? ? ? ? // 將 content 存入到上一步獲取到的 values 中炊林,可能是 matches姥卢,也可能是 mismatches

? ? ? ? ? ? values.add(content);

? ? ? ? } else {

? ? ? ? ? ? throw new ParseException("Illegal route rule ...");

? ? ? ? }

? ? }

? ? return condition;

}

以上就是路由規(guī)則的解析邏輯,該邏輯由正則表達(dá)式 + 一個 while 循環(huán) + 數(shù)個條件分支組成渣聚。下面使用一個示例對解析邏輯進行演繹独榴。示例為?host = 2.2.2.2 & host != 1.1.1.1 & method = hello?。正則解析結(jié)果如下:

? ? 括號一? ? ? 括號二

1.? null? ? ? host

2.? =? ? ? ? 2.2.2.2

3.? &? ? ? ? host

4.? !=? ? ? ? 1.1.1.1

5.? &? ? ? ? method

6.? =? ? ? ? hello

現(xiàn)在線程進入 while 循環(huán):

第一次循環(huán):分隔符 separator = null奕枝,content = “host”棺榔。此時創(chuàng)建 MatchPair 對象,并存入到 condition 中隘道,condition = {“host”: MatchPair@123}

第二次循環(huán):分隔符 separator = “=”症歇,content = “2.2.2.2”,pair = MatchPair@123薄声。此時將 2.2.2.2 放入到 MatchPair@123 對象的 matches 集合中当船。

第三次循環(huán):分隔符 separator = “&”,content = “host”默辨。host 已存在于 condition 中德频,因此 pair = MatchPair@123。

第四次循環(huán):分隔符 separator = “!=”缩幸,content = “1.1.1.1”壹置,pair = MatchPair@123竞思。此時將 1.1.1.1 放入到 MatchPair@123 對象的 mismatches 集合中。

第五次循環(huán):分隔符 separator = “&”钞护,content = “method”盖喷。condition.get(“method”) = null,因此新建一個 MatchPair 對象难咕,并放入到 condition 中课梳。此時 condition = {“host”: MatchPair@123, “method”: MatchPair@ 456}

第六次循環(huán):分隔符 separator = “=”,content = “2.2.2.2”余佃,pair = MatchPair@456暮刃。此時將 hello 放入到 MatchPair@456 對象的 matches 集合中。

循環(huán)結(jié)束爆土,此時 condition 的內(nèi)容如下:

{

? ? "host": {

? ? ? ? "matches": ["2.2.2.2"],

? ? ? ? "mismatches": ["1.1.1.1"]

? ? },

? ? "method": {

? ? ? ? "matches": ["hello"],

? ? ? ? "mismatches": []

? ? }

}

路由規(guī)則的解析過程稍微有點復(fù)雜椭懊,大家可通過 ConditionRouter 的測試類對該邏輯進行測試。并且找一個表達(dá)式步势,對照上面的代碼走一遍氧猬,加深理解。關(guān)于路由規(guī)則的解析過程就先到這坏瘩,我們繼續(xù)往下看盅抚。

2.2 服務(wù)路由

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

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)

? ? ? ? throws RpcException {

? ? if (invokers == null || invokers.isEmpty()) {

? ? ? ? return invokers;

? ? }

? ? try {

? ? ? ? // 先對服務(wù)消費者條件進行匹配泉哈,如果匹配失敗,表明當(dāng)前消費者 url 不符合匹配規(guī)則破讨,

? ? ? ? // 無需進行后續(xù)匹配丛晦,直接返回 Invoker 列表即可。比如下面的規(guī)則:

? ? ? ? //? ? host = 10.20.153.10 => host = 10.0.0.10

? ? ? ? // 這條路由規(guī)則希望 IP 為 10.20.153.10 的服務(wù)消費者調(diào)用 IP 為 10.0.0.10 機器上的服務(wù)提陶。

? ? ? ? // 當(dāng)消費者 ip 為 10.20.153.11 時烫沙,matchWhen 返回 false,表明當(dāng)前這條路由規(guī)則不適用于

? ? ? ? // 當(dāng)前的服務(wù)消費者隙笆,此時無需再進行后續(xù)匹配锌蓄,直接返回即可。

? ? ? ? if (!matchWhen(url, invocation)) {

? ? ? ? ? ? return invokers;

? ? ? ? }

? ? ? ? List<Invoker<T>> result = new ArrayList<Invoker<T>>();

? ? ? ? // 服務(wù)提供者匹配條件未配置撑柔,表明對指定的服務(wù)消費者禁用服務(wù)瘸爽,也就是服務(wù)消費者在黑名單中

? ? ? ? if (thenCondition == null) {

? ? ? ? ? ? logger.warn("The current consumer in the service blacklist...");

? ? ? ? ? ? return result;

? ? ? ? }

? ? ? ? // 這里可以簡單的把 Invoker 理解為服務(wù)提供者,現(xiàn)在使用服務(wù)消費者匹配規(guī)則對

? ? ? ? // Invoker 列表進行匹配

? ? ? ? for (Invoker<T> invoker : invokers) {

? ? ? ? ? ? // 匹配成功铅忿,表明當(dāng)前 Invoker 符合服務(wù)提供者匹配規(guī)則剪决。

? ? ? ? ? ? // 此時將 Invoker 添加到 result 列表中

? ? ? ? ? ? if (matchThen(invoker.getUrl(), url)) {

? ? ? ? ? ? ? ? result.add(invoker);

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? // 返回匹配結(jié)果,如果 result 為空列表,且 force = true柑潦,表示強制返回空列表享言,

? ? ? ? // 否則路由結(jié)果為空的路由規(guī)則將自動失效

? ? ? ? if (!result.isEmpty()) {

? ? ? ? ? ? return result;

? ? ? ? } else if (force) {

? ? ? ? ? ? logger.warn("The route result is empty and force execute ...");

? ? ? ? ? ? return result;

? ? ? ? }

? ? } catch (Throwable t) {

? ? ? ? logger.error("Failed to execute condition router rule: ...");

? ? }


? ? // 原樣返回,此時 force = false渗鬼,表示該條路由規(guī)則失效

? ? return invokers;

}

router 方法先是調(diào)用 matchWhen 對服務(wù)消費者進行匹配览露,如果匹配失敗,直接返回 Invoker 列表譬胎。如果匹配成功差牛,再對服務(wù)提供者進行匹配,匹配邏輯封裝在了 matchThen 方法中银择。下面來看一下這兩個方法的邏輯:

boolean matchWhen(URL url, Invocation invocation) {

? ? // 服務(wù)消費者條件為 null 或空多糠,均返回 true累舷,比如:

? ? //? ? => host != 172.22.3.91

? ? // 表示所有的服務(wù)消費者都不得調(diào)用 IP 為 172.22.3.91 的機器上的服務(wù)

? ? return whenCondition == null || whenCondition.isEmpty()

? ? ? ? || matchCondition(whenCondition, url, null, invocation);? // 進行條件匹配

}

private boolean matchThen(URL url, URL param) {

? ? // 服務(wù)提供者條件為 null 或空浩考,表示禁用服務(wù)

? ? return !(thenCondition == null || thenCondition.isEmpty())

? ? ? ? && matchCondition(thenCondition, url, param, null);? // 進行條件匹配

}

這兩個方法長的有點像,不過邏輯上還是有差別的被盈,大家注意看析孽。這兩個方法均調(diào)用了 matchCondition 方法,不過它們所傳入的參數(shù)是不同的只怎,這個需要特別注意袜瞬。不然后面的邏輯不好弄懂。下面我們對這幾個參數(shù)進行溯源身堡。matchWhen 方法向 matchCondition 方法傳入的參數(shù)為 [whenCondition, url, null, invocation]邓尤,第一個參數(shù) whenCondition 為服務(wù)消費者匹配條件,這個前面分析過贴谎。第二個參數(shù) url 源自 route 方法的參數(shù)列表汞扎,該參數(shù)由外部類調(diào)用 route 方法時傳入。有代碼為證擅这,如下:

private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) {

? ? Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);

? ? List<Router> routers = getRouters();

? ? if (routers != null) {

? ? ? ? for (Router router : routers) {

? ? ? ? ? ? if (router.getUrl() != null) {

? ? ? ? ? ? ? ? // 注意第二個參數(shù)

? ? ? ? ? ? ? ? invokers = router.route(invokers, getConsumerUrl(), invocation);

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? return invokers;

}

上面這段代碼來自 RegistryDirectory澈魄,第二個參數(shù)表示的是服務(wù)消費者 url。matchCondition 的 invocation 參數(shù)也是從這里傳入的仲翎。

接下來再來看看 matchThen 向 matchCondition 方法傳入的參數(shù) [thenCondition, url, param, null]痹扇。第一個參數(shù)不用解釋了。第二個和第三個參數(shù)來自 matchThen 方法的參數(shù)列表溯香,這兩個參數(shù)分別為服務(wù)提供者 url 和服務(wù)消費者 url鲫构。搞清楚這些參數(shù)來源后,接下倆就可以分析 matchCondition 了玫坛。

private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {

? ? // 將服務(wù)提供者或消費者 url 轉(zhuǎn)成 Map

? ? Map<String, String> sample = url.toMap();

? ? boolean result = false;

? ? // 遍歷 condition 列表

? ? for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {

? ? ? ? // 獲取匹配項名稱结笨,比如 host、method 等

? ? ? ? String key = matchPair.getKey();

? ? ? ? String sampleValue;

? ? ? ? // 如果 invocation 不為空,且 key 為 mehtod(s)禀梳,表示進行方法匹配

? ? ? ? if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) {

? ? ? ? ? ? // 從 invocation 獲取調(diào)用方法名稱

? ? ? ? ? ? sampleValue = invocation.getMethodName();

? ? ? ? } else {

? ? ? ? ? ? // 從服務(wù)提供者或消費者 url 中獲取指定字段值杜窄,比如 host、application 等

? ? ? ? ? ? sampleValue = sample.get(key);

? ? ? ? ? ? if (sampleValue == null) {

? ? ? ? ? ? ? ? // 嘗試通過 default.xxx 獲取相應(yīng)的值

? ? ? ? ? ? ? ? sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key);

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? // --------------------:sparkles: 分割線 :sparkles:-------------------- //


? ? ? ? if (sampleValue != null) {

? ? ? ? ? ? // 調(diào)用 MatchPair 的 isMatch 方法進行匹配

? ? ? ? ? ? if (!matchPair.getValue().isMatch(sampleValue, param)) {

? ? ? ? ? ? ? ? // 只要有一個規(guī)則匹配失敗算途,立即返回 false 結(jié)束方法邏輯

? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? result = true;

? ? ? ? ? ? }

? ? ? ? } else {

? ? ? ? ? ? // sampleValue 為空塞耕,表明服務(wù)提供者或消費者 url 中不包含相關(guān)字段。此時如果

? ? ? ? ? ? // MatchPair 的 matches 不為空嘴瓤,表示匹配失敗扫外,返回 false。比如我們有這樣

? ? ? ? ? ? // 一條匹配條件 loadbalance = random廓脆,假設(shè) url 中并不包含 loadbalance 參數(shù)筛谚,

? ? ? ? ? ? // 此時 sampleValue = null。既然路由規(guī)則里限制了 loadbalance = random停忿,

? ? ? ? ? ? // 但 sampleValue = null驾讲,明顯不符合規(guī)則,因此返回 false

? ? ? ? ? ? if (!matchPair.getValue().matches.isEmpty()) {

? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? result = true;

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? return result;

}

如上席赂,matchCondition 方法看起來有點復(fù)雜吮铭,這里簡單縷縷。分割線以上的代碼實際上主要是用于獲取 sampleValue 的值颅停,分割線以下才是進行條件匹配谓晌。條件匹配調(diào)用的邏輯封裝在 isMatch 中,代碼如下:

private boolean isMatch(String value, URL param) {

? ? // 情況一:matches 非空癞揉,mismatches 為空

? ? if (!matches.isEmpty() && mismatches.isEmpty()) {

? ? ? ? // 遍歷 matches 集合纸肉,檢測入?yún)?value 是否能被 matches 集合元素匹配到。

? ? ? ? // 舉個例子喊熟,如果 value = 10.20.153.11柏肪,matches = [10.20.153.*],

? ? ? ? // 此時 isMatchGlobPattern 方法返回 true

? ? ? ? for (String match : matches) {

? ? ? ? ? ? if (UrlUtils.isMatchGlobPattern(match, value, param)) {

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? }

? ? ? ? }


? ? ? ? // 如果所有匹配項都無法匹配到入?yún)ⅲ瑒t返回 false

? ? ? ? return false;

? ? }

? ? // 情況二:matches 為空逊移,mismatches 非空

? ? if (!mismatches.isEmpty() && matches.isEmpty()) {

? ? ? ? for (String mismatch : mismatches) {

? ? ? ? ? ? // 只要入?yún)⒈?mismatches 集合中的任意一個元素匹配到预吆,就返回 false

? ? ? ? ? ? if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {

? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? // mismatches 集合中所有元素都無法匹配到入?yún)ⅲ藭r返回 true

? ? ? ? return true;

? ? }

? ? // 情況三:matches 非空胳泉,mismatches 非空

? ? if (!matches.isEmpty() && !mismatches.isEmpty()) {

? ? ? ? // matches 和 mismatches 均為非空拐叉,此時優(yōu)先使用 mismatches 集合元素對入?yún)⑦M行匹配。

? ? ? ? // 只要 mismatches 集合中任意一個元素與入?yún)⑵ヅ涑晒ι壬蹋土⒓捶祷?false凤瘦,結(jié)束方法邏輯

? ? ? ? for (String mismatch : mismatches) {

? ? ? ? ? ? if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {

? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? // mismatches 集合元素?zé)o法匹配到入?yún)ⅲ藭r使用 matches 繼續(xù)匹配

? ? ? ? for (String match : matches) {

? ? ? ? ? ? // 只要 matches 集合中任意一個元素與入?yún)⑵ヅ涑晒Π钙蹋土⒓捶祷?true

? ? ? ? ? ? if (UrlUtils.isMatchGlobPattern(match, value, param)) {

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return false;

? ? }


? ? // 情況四:matches 和 mismatches 均為空蔬芥,此時返回 false

? ? return false;

}

isMatch 方法邏輯比較清晰,由三個條件分支組成,用于處理四種情況笔诵。這里對四種情況下的匹配邏輯進行簡單的總結(jié)返吻,如下:

條件動作情況一matches 非空,mismatches 為空遍歷 matches 集合元素乎婿,并與入?yún)⑦M行匹配测僵。只要有一個元素成功匹配入?yún)ⅲ纯煞祷?true谢翎。若全部失配捍靠,則返回 false。情況二matches 為空森逮,mismatches 非空遍歷 mismatches 集合元素榨婆,并與入?yún)⑦M行匹配。只要有一個元素成功匹配入?yún)啵⒓?false良风。若全部失配,則返回 true璃搜。情況三matches 非空拖吼,mismatches 非空優(yōu)先使用 mismatches 集合元素對入?yún)⑦M行匹配,只要任一元素與入?yún)⑵ヅ涑晒φ馕牵土⒓捶祷?false,結(jié)束方法邏輯篙议。否則再使用 matches 中的集合元素進行匹配唾糯,只要有任意一個元素匹配成功,即可返回 true鬼贱。若全部失配移怯,則返回 false情況四matches 為空,mismatches 為空直接返回 false

isMatch 方法邏輯不是很難理解这难,大家自己再看看舟误。下面繼續(xù)分析 isMatchGlobPattern 方法。

public static boolean isMatchGlobPattern(String pattern, String value, URL param) {

? ? if (param != null && pattern.startsWith("$")) {

? ? ? ? // 引用服務(wù)消費者參數(shù)姻乓,param 參數(shù)為服務(wù)消費者 url

? ? ? ? pattern = param.getRawParameter(pattern.substring(1));

? ? }

? ? // 調(diào)用重載方法繼續(xù)比較

? ? return isMatchGlobPattern(pattern, value);

}

public static boolean isMatchGlobPattern(String pattern, String value) {

? ? // 對 * 通配符提供支持

? ? if ("*".equals(pattern))

? ? ? ? // 匹配規(guī)則為通配符 *嵌溢,直接返回 true 即可

? ? ? ? return true;

? ? if ((pattern == null || pattern.length() == 0)

? ? ? ? ? ? && (value == null || value.length() == 0))

? ? ? ? // pattern 和 value 均為空,此時可認(rèn)為兩者相等蹋岩,返回 true

? ? ? ? return true;

? ? if ((pattern == null || pattern.length() == 0)

? ? ? ? ? ? || (value == null || value.length() == 0))

? ? ? ? // pattern 和 value 其中有一個為空赖草,兩者不相等,返回 false

? ? ? ? return false;

? ? // 查找 * 通配符位置

? ? int i = pattern.lastIndexOf('*');

? ? if (i == -1) {

? ? ? ? // 匹配規(guī)則中不包含通配符剪个,此時直接比較 value 和 pattern 是否相等即可秧骑,并返回比較結(jié)果

? ? ? ? return value.equals(pattern);

? ? }

? ? // 通配符 "*" 在匹配規(guī)則尾部,比如 10.0.21.*

? ? else if (i == pattern.length() - 1) {

? ? ? ? // 檢測 value 是否以不含通配符的匹配規(guī)則開頭,并返回結(jié)果乎折。比如:

? ? ? ? // pattern = 10.0.21.*绒疗,value = 10.0.21.12,此時返回 true

? ? ? ? return value.startsWith(pattern.substring(0, i));

? ? }

? ? // 通配符 "*" 在匹配規(guī)則頭部

? ? else if (i == 0) {

? ? ? ? // 檢測 value 是否以不含通配符的匹配規(guī)則結(jié)尾骂澄,并返回結(jié)果

? ? ? ? return value.endsWith(pattern.substring(i + 1));

? ? }

? ? // 通配符 "*" 在匹配規(guī)則中間位置

? ? else {

? ? ? ? // 通過通配符將 pattern 分成兩半忌堂,得到 prefix 和 suffix

? ? ? ? String prefix = pattern.substring(0, i);

? ? ? ? String suffix = pattern.substring(i + 1);

? ? ? ? // 檢測 value 是否以 prefix 變量開頭,且以 suffix 變量結(jié)尾酗洒,并返回結(jié)果

? ? ? ? return value.startsWith(prefix) && value.endsWith(suffix);

? ? }

}

以上就是 isMatchGlobPattern 兩個重載方法的全部邏輯士修,這兩個方法分別對普通的匹配,以及”引用消費者參數(shù)“和通配符匹配做了支持樱衷。這兩個方法的邏輯并不是很復(fù)雜棋嘲,而且我也在代碼上進行了比較詳細(xì)的注釋,大家自己看看吧矩桂,就不多說了沸移。

3. 總結(jié)

本篇文章對條件路由的表達(dá)式解析和服務(wù)路由過程進行了較為細(xì)致的分析≈读瘢總的來說雹锣,條件路由的代碼還是有一些復(fù)雜的,需要耐下心來看癞蚕。在閱讀條件路由代碼的過程中蕊爵,要多調(diào)試。一般的框架都會有單元測試桦山,Dubbo 也不例外攒射,因此大家可以直接通過 ConditionRouterTest 對條件路由進行調(diào)試,無需自己手寫測試用例恒水。

好了会放,關(guān)于條件路由就先分析到這,謝謝閱讀钉凌。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咧最,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子御雕,更是在濱河造成了極大的恐慌矢沿,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饮笛,死亡現(xiàn)場離奇詭異咨察,居然都是意外死亡,警方通過查閱死者的電腦和手機福青,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門摄狱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脓诡,“玉大人,你說我怎么就攤上這事媒役∽Q瑁” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵酣衷,是天一觀的道長交惯。 經(jīng)常有香客問我,道長穿仪,這世上最難降的妖魔是什么席爽? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮啊片,結(jié)果婚禮上只锻,老公的妹妹穿的比我還像新娘。我一直安慰自己紫谷,他們只是感情好齐饮,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著笤昨,像睡著了一般祖驱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瞒窒,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天捺僻,我揣著相機與錄音,去河邊找鬼根竿。 笑死陵像,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寇壳。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼妻怎,長吁一口氣:“原來是場噩夢啊……” “哼壳炎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逼侦,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤匿辩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后榛丢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铲球,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年晰赞,在試婚紗的時候發(fā)現(xiàn)自己被綠了稼病。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片选侨。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖然走,靈堂內(nèi)的尸體忽然破棺而出援制,到底是詐尸還是另有隱情,我是刑警寧澤芍瑞,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布晨仑,位于F島的核電站,受9級特大地震影響拆檬,放射性物質(zhì)發(fā)生泄漏洪己。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一竟贯、第九天 我趴在偏房一處隱蔽的房頂上張望答捕。 院中可真熱鬧,春花似錦澄耍、人聲如沸噪珊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痢站。三九已至,卻和暖如春选酗,著一層夾襖步出監(jiān)牢的瞬間阵难,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工芒填, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呜叫,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓殿衰,卻偏偏與公主長得像朱庆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子闷祥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理娱颊,服務(wù)發(fā)現(xiàn),斷路器凯砍,智...
    卡卡羅2017閱讀 134,637評論 18 139
  • 先看官網(wǎng)兩張圖【引用來自官網(wǎng)】:image.png 官網(wǎng)說明: 1.首先 ReferenceConfig 類的 i...
    致慮閱讀 1,024評論 0 2
  • 什么是事務(wù)箱硕? 個人認(rèn)為,事務(wù)可以理解為凡是需要通過運用“時間悟衩,腦力剧罩,肢體”等資源去處理的事情都可以理解為事...
    巽迎論道閱讀 348評論 0 1
  • 現(xiàn)在存在的問題 如果一個APP中可以根據(jù)用戶喜好,更改APP中圖片的顏色座泳,則設(shè)計師必須重復(fù)修改圖片惠昔,開發(fā)者圖片文件...
    叫我小黑閱讀 1,260評論 0 1
  • 大學(xué)的時光幕与,在她的記憶里似乎都是不美好的,因為所有的一切他都無法參與舰罚。她的開心纽门,她的難過,她的生日营罢,她的迷茫赏陵,她的...
    荼蘼Gemiliy閱讀 209評論 0 0