目錄
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;
}