1. 簡(jiǎn)介
上一篇文章分析了集群容錯(cuò)的第一部分 – 服務(wù)目錄 Directory。服務(wù)目錄在刷新 Invoker 列表的過(guò)程中,會(huì)通過(guò) Router 進(jìn)行服務(wù)路由。上一篇文章關(guān)于服務(wù)路由相關(guān)邏輯沒(méi)有細(xì)致分析,一筆帶過(guò)了,本篇文章將對(duì)此進(jìn)行詳細(xì)的分析。首先,先來(lái)介紹一下服務(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)簽路由暫未在我所分析的 2.6.4 版本中提供,該實(shí)現(xiàn)會(huì)在 2.7.0 版本中提供。本篇文章將分析條件路由相關(guān)源碼,腳本路由和標(biāo)簽路由這里就不分析了。下面進(jìn)入正題。
2. 源碼分析
條件路由規(guī)則有兩個(gè)條件組成,分別用于對(duì)服務(wù)消費(fèi)者和提供者進(jìn)行匹配。比如有這樣一條規(guī)則:
host = 10.20.153.10 => host = 10.20.153.11
該條規(guī)則表示 IP 為 10.20.153.10 的服務(wù)消費(fèi)者只可調(diào)用 IP 為 10.20.153.11 機(jī)器上的服務(wù),不可調(diào)用其他機(jī)器上的服務(wù)。條件路由規(guī)則的格式如下:
[服務(wù)消費(fèi)者匹配條件] => [服務(wù)提供者匹配條件]
如果服務(wù)消費(fèi)者匹配條件為空,表示不對(duì)服務(wù)消費(fèi)者進(jìn)行限制。如果服務(wù)提供者匹配條件為空,表示對(duì)某些服務(wù)消費(fèi)者禁用服務(wù)。Dubbo 官方文檔對(duì)條件路由進(jìn)行了比較詳細(xì)的介紹,大家可以參考下,這里就不過(guò)多說(shuō)明了。
條件路由實(shí)現(xiàn)類 ConditionRouter 需要對(duì)用戶配置的路由規(guī)則進(jìn)行解析,得到一系列的條件。然后再根據(jù)這些條件對(duì)服務(wù)進(jìn)行路由。本章將分兩節(jié)進(jìn)行說(shuō)明,2.1節(jié)介紹表達(dá)式解析過(guò)程。2.2 節(jié)介紹服務(wù)路由的過(guò)程。接下來(lái),我們先從表達(dá)式解析過(guò)程看起。
2.1 表達(dá)式解析
條件路由規(guī)則是一條字符串,對(duì)于 Dubbo 來(lái)說(shuō),它并不能直接理解字符串的意思,需要將其解析成內(nèi)部格式才行。條件表達(dá)式的解析過(guò)程始于 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ù)消費(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);
? ? ? ? this.whenCondition = when;
? ? ? ? this.thenCondition = then;
? ? } catch (ParseException e) {
? ? ? ? throw new IllegalStateException(e.getMessage(), e);
? ? }
}
如上,ConditionRouter 構(gòu)造方法先是對(duì)路由規(guī)則做預(yù)處理,然后調(diào)用 parseRule 方法分別對(duì)服務(wù)提供者和消費(fèi)者規(guī)則進(jìn)行解析,最后將解析結(jié)果賦值給 whenCondition 和 thenCondition 成員變量。ConditionRouter 構(gòu)造方法不是很復(fù)雜,這里就不多說(shuō)了。下面我們把重點(diǎn)放在 parseRule 方法上,在詳細(xì)介紹這個(gè)方法之前,我們先來(lái)看一個(gè)內(nèi)部類。
private static final class MatchPair {
? ? final Set<String> matches = new HashSet<String>();
? ? final Set<String> mismatches = new HashSet<String>();
}
MatchPair 內(nèi)部包含了兩個(gè) Set 型的成員變量,分別用于存放匹配和不匹配的條件。這個(gè)類兩個(gè)成員變量會(huì)在 parseRule 方法中被用到,下面來(lái)看一下。
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;
? ? // 通過(guò)正則表達(dá)式匹配路由規(guī)則,ROUTE_PATTERN = ([&!=,]*)\s*([^&!=,\s]+)
? ? // 這個(gè)表達(dá)式看起來(lái)不是很好理解,第一個(gè)括號(hào)內(nèi)的表達(dá)式用于匹配"&", "!", "=" 和 "," 等符號(hào)。
? ? // 第二括號(hào)內(nèi)的用于匹配英文字母,數(shù)字等字符。舉個(gè)例子說(shuō)明一下:
? ? //? ? 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()) {
? ? ? // 獲取括號(hào)一內(nèi)的匹配結(jié)果
? ? ? ? String separator = matcher.group(1);
? ? ? ? // 獲取括號(hào)二內(nèi)的匹配結(jié)果
? ? ? ? String content = matcher.group(2);
? ? ? ? // 分隔符為空,表示匹配的是表達(dá)式的開(kāi)始部分
? ? ? ? if (separator == null || separator.length() == 0) {
? ? ? ? ? ? // 創(chuàng)建 MatchPair 對(duì)象
? ? ? ? ? ? pair = new MatchPair();
? ? ? ? ? ? // 存儲(chǔ) <匹配項(xiàng), MatchPair> 鍵值對(duì),比如 <host, MatchPair>
? ? ? ? ? ? condition.put(content, pair);
? ? ? ? }
? ? ? ? // 如果分隔符為 &,表明接下來(lái)也是一個(gè)條件
? ? ? ? else if ("&".equals(separator)) {
? ? ? ? ? ? // 嘗試從 condition 獲取 MatchPair
? ? ? ? ? ? if (condition.get(content) == null) {
? ? ? ? ? ? ? ? // 未獲取到 MatchPair,重新創(chuàng)建一個(gè),并放入 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á)式 + 一個(gè) while 循環(huán) + 數(shù)個(gè)條件分支組成。下面使用一個(gè)示例對(duì)解析邏輯進(jìn)行演繹。示例為?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
現(xiàn)在線程進(jìn)入 while 循環(huán):
第一次循環(huán):分隔符 separator = null,content = “host”。此時(shí)創(chuàng)建 MatchPair 對(duì)象,并存入到 condition 中,condition = {“host”: MatchPair@123}
第二次循環(huán):分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@123。此時(shí)將 2.2.2.2 放入到 MatchPair@123 對(duì)象的 matches 集合中。
第三次循環(huán):分隔符 separator = “&”,content = “host”。host 已存在于 condition 中,因此 pair = MatchPair@123。
第四次循環(huán):分隔符 separator = “!=”,content = “1.1.1.1”,pair = MatchPair@123。此時(shí)將 1.1.1.1 放入到 MatchPair@123 對(duì)象的 mismatches 集合中。
第五次循環(huán):分隔符 separator = “&”,content = “method”。condition.get(“method”) = null,因此新建一個(gè) MatchPair 對(duì)象,并放入到 condition 中。此時(shí) condition = {“host”: MatchPair@123, “method”: MatchPair@ 456}
第六次循環(huán):分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@456。此時(shí)將 hello 放入到 MatchPair@456 對(duì)象的 matches 集合中。
循環(huán)結(jié)束,此時(shí) condition 的內(nèi)容如下:
{
? ? "host": {
? ? ? ? "matches": ["2.2.2.2"],
? ? ? ? "mismatches": ["1.1.1.1"]
? ? },
? ? "method": {
? ? ? ? "matches": ["hello"],
? ? ? ? "mismatches": []
? ? }
}
路由規(guī)則的解析過(guò)程稍微有點(diǎn)復(fù)雜,大家可通過(guò) ConditionRouter 的測(cè)試類對(duì)該邏輯進(jìn)行測(cè)試。并且找一個(gè)表達(dá)式,對(duì)照上面的代碼走一遍,加深理解。關(guān)于路由規(guī)則的解析過(guò)程就先到這,我們繼續(xù)往下看。
2.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 (invokers == null || invokers.isEmpty()) {
? ? ? ? return invokers;
? ? }
? ? try {
? ? ? ? // 先對(duì)服務(wù)消費(fèi)者條件進(jìn)行匹配,如果匹配失敗,表明當(dāng)前消費(fèi)者 url 不符合匹配規(guī)則,
? ? ? ? // 無(wú)需進(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í)無(wú)需再進(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...");
? ? ? ? ? ? return result;
? ? ? ? }
? ? ? ? // 這里可以簡(jiǎn)單的把 Invoker 理解為服務(wù)提供者,現(xiàn)在使用服務(wù)消費(fèi)者匹配規(guī)則對(duì)
? ? ? ? // Invoker 列表進(jìn)行匹配
? ? ? ? for (Invoker<T> invoker : invokers) {
? ? ? ? ? ? // 匹配成功,表明當(dāng)前 Invoker 符合服務(wù)提供者匹配規(guī)則。
? ? ? ? ? ? // 此時(shí)將 Invoker 添加到 result 列表中
? ? ? ? ? ? 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 ...");
? ? ? ? ? ? return result;
? ? ? ? }
? ? } catch (Throwable t) {
? ? ? ? logger.error("Failed to execute condition router rule: ...");
? ? }
? ? // 原樣返回,此時(shí) force = false,表示該條路由規(guī)則失效
? ? return invokers;
}
router 方法先是調(diào)用 matchWhen 對(duì)服務(wù)消費(fèi)者進(jìn)行匹配,如果匹配失敗,直接返回 Invoker 列表。如果匹配成功,再對(duì)服務(wù)提供者進(jìn)行匹配,匹配邏輯封裝在了 matchThen 方法中。下面來(lái)看一下這兩個(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 whenCondition == null || whenCondition.isEmpty()
? ? ? ? || matchCondition(whenCondition, url, null, invocation);? // 進(jìn)行條件匹配
}
private boolean matchThen(URL url, URL param) {
? ? // 服務(wù)提供者條件為 null 或空,表示禁用服務(wù)
? ? return !(thenCondition == null || thenCondition.isEmpty())
? ? ? ? && matchCondition(thenCondition, url, param, null);? // 進(jìn)行條件匹配
}
這兩個(gè)方法長(zhǎng)的有點(diǎn)像,不過(guò)邏輯上還是有差別的,大家注意看。這兩個(gè)方法均調(diào)用了 matchCondition 方法,不過(guò)它們所傳入的參數(shù)是不同的,這個(gè)需要特別注意。不然后面的邏輯不好弄懂。下面我們對(duì)這幾個(gè)參數(shù)進(jìn)行溯源。matchWhen 方法向 matchCondition 方法傳入的參數(shù)為 [whenCondition, url, null, invocation],第一個(gè)參數(shù) whenCondition 為服務(wù)消費(fèi)者匹配條件,這個(gè)前面分析過(guò)。第二個(gè)參數(shù) url 源自 route 方法的參數(shù)列表,該參數(shù)由外部類調(diào)用 route 方法時(shí)傳入。有代碼為證,如下:
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) {
? ? ? ? ? ? ? ? // 注意第二個(gè)參數(shù)
? ? ? ? ? ? ? ? invokers = router.route(invokers, getConsumerUrl(), invocation);
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? return invokers;
}
上面這段代碼來(lái)自 RegistryDirectory,第二個(gè)參數(shù)表示的是服務(wù)消費(fèi)者 url。matchCondition 的 invocation 參數(shù)也是從這里傳入的。
接下來(lái)再來(lái)看看 matchThen 向 matchCondition 方法傳入的參數(shù) [thenCondition, url, param, null]。第一個(gè)參數(shù)不用解釋了。第二個(gè)和第三個(gè)參數(shù)來(lái)自 matchThen 方法的參數(shù)列表,這兩個(gè)參數(shù)分別為服務(wù)提供者 url 和服務(wù)消費(fèi)者 url。搞清楚這些參數(shù)來(lái)源后,接下倆就可以分析 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;
? ? ? ? // 如果 invocation 不為空,且 key 為 mehtod(s),表示進(jìn)行方法匹配
? ? ? ? if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) {
? ? ? ? ? ? // 從 invocation 獲取調(diào)用方法名稱
? ? ? ? ? ? sampleValue = invocation.getMethodName();
? ? ? ? } else {
? ? ? ? ? ? // 從服務(wù)提供者或消費(fèi)者 url 中獲取指定字段值,比如 host、application 等
? ? ? ? ? ? sampleValue = sample.get(key);
? ? ? ? ? ? if (sampleValue == null) {
? ? ? ? ? ? ? ? // 嘗試通過(guò) default.xxx 獲取相應(yīng)的值
? ? ? ? ? ? ? ? sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // --------------------:sparkles: 分割線 :sparkles:-------------------- //
? ? ? ? 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 {
? ? ? ? ? ? // 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;
}
如上,matchCondition 方法看起來(lái)有點(diǎn)復(fù)雜,這里簡(jiǎn)單縷縷。分割線以上的代碼實(shí)際上主要是用于獲取 sampleValue 的值,分割線以下才是進(jìn)行條件匹配。條件匹配調(diào)用的邏輯封裝在 isMatch 中,代碼如下:
private boolean isMatch(String value, URL param) {
? ? // 情況一:matches 非空,mismatches 為空
? ? if (!matches.isEmpty() && mismatches.isEmpty()) {
? ? ? ? // 遍歷 matches 集合,檢測(cè)入?yún)?value 是否能被 matches 集合元素匹配到。
? ? ? ? // 舉個(gè)例子,如果 value = 10.20.153.11,matches = [10.20.153.*],
? ? ? ? // 此時(shí) isMatchGlobPattern 方法返回 true
? ? ? ? for (String match : matches) {
? ? ? ? ? ? if (UrlUtils.isMatchGlobPattern(match, value, param)) {
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 如果所有匹配項(xiàng)都無(wú)法匹配到入?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;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // mismatches 集合中所有元素都無(wú)法匹配到入?yún)ⅲ藭r(shí)返回 true
? ? ? ? return true;
? ? }
? ? // 情況三:matches 非空,mismatches 非空
? ? if (!matches.isEmpty() && !mismatches.isEmpty()) {
? ? ? ? // 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;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return false;
? ? }
? ? // 情況四:matches 和 mismatches 均為空,此時(shí)返回 false
? ? return false;
}
isMatch 方法邏輯比較清晰,由三個(gè)條件分支組成,用于處理四種情況。這里對(duì)四種情況下的匹配邏輯進(jìn)行簡(jiǎn)單的總結(jié),如下:
條件動(dòng)作情況一matches 非空,mismatches 為空遍歷 matches 集合元素,并與入?yún)⑦M(jìn)行匹配。只要有一個(gè)元素成功匹配入?yún)ⅲ纯煞祷?true。若全部失配,則返回 false。情況二matches 為空,mismatches 非空遍歷 mismatches 集合元素,并與入?yún)⑦M(jìn)行匹配。只要有一個(gè)元素成功匹配入?yún)ⅲ⒓?false。若全部失配,則返回 true。情況三matches 非空,mismatches 非空優(yōu)先使用 mismatches 集合元素對(duì)入?yún)⑦M(jìn)行匹配,只要任一元素與入?yún)⑵ヅ涑晒Γ土⒓捶祷?false,結(jié)束方法邏輯。否則再使用 matches 中的集合元素進(jìn)行匹配,只要有任意一個(gè)元素匹配成功,即可返回 true。若全部失配,則返回 false情況四matches 為空,mismatches 為空直接返回 false
isMatch 方法邏輯不是很難理解,大家自己再看看。下面繼續(xù)分析 isMatchGlobPattern 方法。
public static boolean isMatchGlobPattern(String pattern, String value, URL param) {
? ? if (param != null && pattern.startsWith("$")) {
? ? ? ? // 引用服務(wù)消費(fèi)者參數(shù),param 參數(shù)為服務(wù)消費(fèi)者 url
? ? ? ? pattern = param.getRawParameter(pattern.substring(1));
? ? }
? ? // 調(diào)用重載方法繼續(xù)比較
? ? return isMatchGlobPattern(pattern, value);
}
public static boolean isMatchGlobPattern(String pattern, String value) {
? ? // 對(duì) * 通配符提供支持
? ? if ("*".equals(pattern))
? ? ? ? // 匹配規(guī)則為通配符 *,直接返回 true 即可
? ? ? ? return true;
? ? if ((pattern == null || pattern.length() == 0)
? ? ? ? ? ? && (value == null || value.length() == 0))
? ? ? ? // pattern 和 value 均為空,此時(shí)可認(rèn)為兩者相等,返回 true
? ? ? ? return true;
? ? if ((pattern == null || pattern.length() == 0)
? ? ? ? ? ? || (value == null || value.length() == 0))
? ? ? ? // pattern 和 value 其中有一個(gè)為空,兩者不相等,返回 false
? ? ? ? return false;
? ? // 查找 * 通配符位置
? ? int i = pattern.lastIndexOf('*');
? ? if (i == -1) {
? ? ? ? // 匹配規(guī)則中不包含通配符,此時(shí)直接比較 value 和 pattern 是否相等即可,并返回比較結(jié)果
? ? ? ? return value.equals(pattern);
? ? }
? ? // 通配符 "*" 在匹配規(guī)則尾部,比如 10.0.21.*
? ? else if (i == pattern.length() - 1) {
? ? ? ? // 檢測(cè) value 是否以不含通配符的匹配規(guī)則開(kāi)頭,并返回結(jié)果。比如:
? ? ? ? // pattern = 10.0.21.*,value = 10.0.21.12,此時(shí)返回 true
? ? ? ? return value.startsWith(pattern.substring(0, i));
? ? }
? ? // 通配符 "*" 在匹配規(guī)則頭部
? ? else if (i == 0) {
? ? ? ? // 檢測(cè) value 是否以不含通配符的匹配規(guī)則結(jié)尾,并返回結(jié)果
? ? ? ? return value.endsWith(pattern.substring(i + 1));
? ? }
? ? // 通配符 "*" 在匹配規(guī)則中間位置
? ? else {
? ? ? ? // 通過(guò)通配符將 pattern 分成兩半,得到 prefix 和 suffix
? ? ? ? String prefix = pattern.substring(0, i);
? ? ? ? String suffix = pattern.substring(i + 1);
? ? ? ? // 檢測(cè) value 是否以 prefix 變量開(kāi)頭,且以 suffix 變量結(jié)尾,并返回結(jié)果
? ? ? ? return value.startsWith(prefix) && value.endsWith(suffix);
? ? }
}
以上就是 isMatchGlobPattern 兩個(gè)重載方法的全部邏輯,這兩個(gè)方法分別對(duì)普通的匹配,以及”引用消費(fèi)者參數(shù)“和通配符匹配做了支持。這兩個(gè)方法的邏輯并不是很復(fù)雜,而且我也在代碼上進(jìn)行了比較詳細(xì)的注釋,大家自己看看吧,就不多說(shuō)了。
3. 總結(jié)
本篇文章對(duì)條件路由的表達(dá)式解析和服務(wù)路由過(guò)程進(jìn)行了較為細(xì)致的分析。總的來(lái)說(shuō),條件路由的代碼還是有一些復(fù)雜的,需要耐下心來(lái)看。在閱讀條件路由代碼的過(guò)程中,要多調(diào)試。一般的框架都會(huì)有單元測(cè)試,Dubbo 也不例外,因此大家可以直接通過(guò) ConditionRouterTest 對(duì)條件路由進(jìn)行調(diào)試,無(wú)需自己手寫測(cè)試用例。
好了,關(guān)于條件路由就先分析到這,謝謝閱讀。