Dubbo路由機制是在服務間的調用時,通過將服務提供者按照設定的路由規則來決定調用哪一個具體的服務。
路由服務結構
路由實現工廠類是在 router 包下
由于 RouterFactory 是 SPI 接口,同時在獲取路由 RouterFactory#getRouter 方法上有 @Adaptive(“protocol”) 注解,所以在獲取路由的時候會動態調用需要的工廠類。
可以看到 getRouter 方法返回的是一個 Router 接口,該接口信息如下
其中 Router#route 是服務路由的入口,對于不同類型的路由工廠,有特定的 Router 實現類。
以上就是通過解析 URL,獲取到具體的 Router,通過調用 Router#router 過濾出符合當前路由規則的 invokers。
服務路由實現
上面展示了路由實現類,這幾個實現類型中,ConditionRouter 條件路由是最為常用的類型,由于文章篇幅有限,本文就不對全部的路由類型逐一分析,只對條件路由進行具體分析,只要弄懂這一個類型,其它類型的解析就能容易掌握。
條件路由參數規則
在分析條件路由前,先了解條件路由的參數配置,官方文檔如下:
條件路由規則內容如下:
條件路由實現分析
分析路由實現,主要分析工廠類的 xxxRouterFactory#getRouter 和 xxxRouter#route 方法。
ConditionRouterFactory#getRouter
ConditionRouterFactory 中通過創建 ConditionRouter 對象來初始化解析相關參數配置。
在 ConditionRouter 構造函數中,從 URL 里獲取 rule 的字符串格式的規則,解析規則在 ConditionRouter#init 初始化方法中。
public void init(String rule) {
try {
if (rule == null || rule.trim().length() == 0) {
throw new IllegalArgumentException(“Illegal route rule!”);
}
// 去掉 consumer. 和 provider. 的標識
rule = rule.replace(“consumer.”, “”).replace(“provider.”, “”);
// 獲取 消費者匹配條件 和 提供者地址匹配條件 的分隔符
int i = rule.indexOf(“=》”);
// 消費者匹配條件
String whenRule = i 《 0 ? null : rule.substring(0, i).trim();
// 提供者地址匹配條件
String thenRule = i 《 0 ? rule.trim() : rule.substring(i + 2).trim();
// 解析消費者路由規則
Map《String, MatchPair》 when = StringUtils.isBlank(whenRule) || “true”.equals(whenRule) ? new HashMap《String, MatchPair》() : parseRule(whenRule);
// 解析提供者路由規則
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);
}
}
以路由規則字符串中的=》為分隔符,將消費者匹配條件和提供者匹配條件分割,解析兩個路由規則后,賦值給當前對象的變量。
調用 parseRule 方法來解析消費者和服務者路由規則。
// 正則驗證路由規則
protected static final Pattern ROUTE_PATTERN = Pattern.compile(“([&!=,]*)\s*([^&!=,\s]+)”);
private static Map《String, MatchPair》 parseRule(String rule)
throws ParseException {
/**
* 條件變量和條件變量值的映射關系
* 比如 host =》 127.0.0.1 則保存著 host 和 127.0.0.1 的映射關系
*/
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;
final Matcher matcher = ROUTE_PATTERN.matcher(rule);
while (matcher.find()) {
// 獲取正則前部分匹配(第一個括號)的內容
String separator = matcher.group(1);
// 獲取正則后部分匹配(第二個括號)的內容
String content = matcher.group(2);
// 如果獲取前部分為空,則表示規則開始位置,則當前 content 必為條件變量
if (StringUtils.isEmpty(separator)) {
pair = new MatchPair();
condition.put(content, pair);
}
// 如果分隔符是 &,則 content 為條件變量
else if (“&”.equals(separator)) {
// 當前 content 是條件變量,用來做映射集合的 key 的,如果沒有則添加一個元素
if (condition.get(content) == null) {
pair = new MatchPair();
condition.put(content, pair);
} else {
pair = condition.get(content);
}
}
// 如果當前分割符是 = ,則當前 content 為條件變量值
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());
}
// 由于 pair 還沒有被重新初始化,所以還是上一個條件變量的對象,所以可以將當前條件變量值在引用對象上賦值
values = pair.matches;
values.add(content);
}
// 如果當前分割符是 = ,則當前 content 也是條件變量值
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);
}
// 如果當前分割符為 ‘,’,則當前 content 也為條件變量值
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());
}
// 直接向條件變量值集合中添加數據
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;
}
上面就是解析條件路由規則的過程,條件變量的值都保存在 MatchPair 中的 matches、mismatches 屬性中,=和,的條件變量值放在可以匹配的 matches 中,!=的條件變量值放在不可匹配路由規則的 mismatches 中。賦值過程中,代碼還是比較優雅。
實際上 matches、mismatches 就是保存的是條件變量值。
ConditionRouter#route
Router#route的作用就是匹配出符合路由規則的 Invoker 集合。
// 在初始化中進行被復制的變量
// 消費者條件匹配規則
protected Map《String, MatchPair》 whenCondition;
// 提供者條件匹配規則
protected Map《String, MatchPair》 thenCondition;
public 《T》 List《Invoker《T》》 route(List《Invoker《T》》 invokers, URL url, Invocation invocation)
throws RpcException {
if (!enabled) {
return invokers;
}
// 驗證 invokers 是否為空
if (CollectionUtils.isEmpty(invokers)) {
return invokers;
}
try {
// 校驗消費者是否有規則匹配,如果沒有則返回傳入的 Invoker
if (!matchWhen(url, invocation)) {
return invokers;
}
List《Invoker《T》》 result = new ArrayList《Invoker《T》》();
if (thenCondition == null) {
logger.warn(“The current consumer in the service blacklist. consumer: ” + NetUtils.getLocalHost() + “, service: ” + url.getServiceKey());
return result;
}
// 遍歷傳入的 invokers,匹配提供者是否有規則匹配
for (Invoker《T》 invoker : invokers) {
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
// 如果 result 不為空,或當前對象 force=true 則返回 result 的 Invoker 列表
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;
}
上面代碼可以看到,只要消費者沒有匹配的規則或提供者沒有匹配的規則及 force=false 時,不會返回傳入的參數的 Invoker。
匹配消費者路由規則和提供者路由規則方法是 matchWhen 和 matchThen
這兩個匹配方法都是調用同一個方法 matchCondition 實現的。將消費者或提供者 URL 轉為 Map,然后與 whenCondition 或 thenCondition 進行匹配。
匹配過程中,如果 key (即 sampleValue 值)存在對應的值,則通過 MatchPair#isMatch 方法再進行匹配。
private boolean isMatch(String value, URL param) {
// 存在可匹配的規則,不存在不可匹配的規則
if (!matches.isEmpty() && mismatches.isEmpty()) {
// 不可匹配的規則列表為空時,只要可匹配的規則匹配上,直接返回 true
for (String match : matches) {
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
return true;
}
}
return false;
}
// 存在不可匹配的規則,不存在可匹配的規則
if (!mismatches.isEmpty() && matches.isEmpty()) {
// 不可匹配的規則列表中存在,則返回false
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
return false;
}
}
return true;
}
// 存在可匹配的規則,也存在不可匹配的規則
if (!matches.isEmpty() && !mismatches.isEmpty()) {
// 都不為空時,不可匹配的規則列表中存在,則返回 false
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
return false;
}
}
for (String match : matches) {
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
return true;
}
}
return false;
}
// 最后剩下的是 可匹配規則和不可匹配規則都為空時
return false;
}
匹配過程再調用 UrlUtils#isMatchGlobPattern 實現
public static boolean isMatchGlobPattern(String pattern, String value, URL param) {
// 如果以 $ 開頭,則獲取 URL 中對應的值
if (param != null && pattern.startsWith(“$”)) {
pattern = param.getRawParameter(pattern.substring(1));
}
//
return isMatchGlobPattern(pattern, value);
}
public static boolean isMatchGlobPattern(String pattern, String value) {
if (“*”.equals(pattern)) {
return true;
}
if (StringUtils.isEmpty(pattern) && StringUtils.isEmpty(value)) {
return true;
}
if (StringUtils.isEmpty(pattern) || StringUtils.isEmpty(value)) {
return false;
}
// 獲取通配符位置
int i = pattern.lastIndexOf(‘*’);
// 如果value中沒有 “*” 通配符,則整個字符串值匹配
if (i == -1) {
return value.equals(pattern);
}
// 如果 “*” 在最后面,則匹配字符串 “*” 之前的字符串即可
else if (i == pattern.length() - 1) {
return value.startsWith(pattern.substring(0, i));
}
// 如果 “*” 在最前面,則匹配字符串 “*” 之后的字符串即可
else if (i == 0) {
return value.endsWith(pattern.substring(i + 1));
}
// 如果 “*” 不在字符串兩端,則同時匹配字符串 “*” 左右兩邊的字符串
else {
String prefix = pattern.substring(0, i);
String suffix = pattern.substring(i + 1);
return value.startsWith(prefix) && value.endsWith(suffix);
}
}
就這樣完成全部的條件路由規則匹配,雖然看似代碼較為繁雜,但是理清規則、思路,一步一步還是較好解析,前提是要熟悉相關參數的用法及形式,不然代碼較難理解。
最后
單純從邏輯上,如果能夠掌握條件路由的實現,去研究其它方式的路由實現,相信不會有太大問題。只是例如像腳本路由的實現,你得先會使用腳本執行引擎為前提,不然就不理解它的代碼。最后,在 dubbo-admin 上可以設置路由,大家可以嘗試各種使用規則,通過實操才能更好掌握和理解路由機制的實現。
責任編輯:gt
-
接口
+關注
關注
33文章
8691瀏覽量
151918 -
路由
+關注
關注
0文章
278瀏覽量
41931
發布評論請先 登錄
相關推薦
聊聊Dubbo - Dubbo可擴展機制實戰
聊聊Dubbo - Dubbo可擴展機制源碼解析
Dubbo開源現狀與未來規劃
攜程的 Dubbo 之路
Dubbo源代碼實現服務調用的動態代理和負載均衡
多態路由機制研究
![多態<b class='flag-5'>路由</b><b class='flag-5'>機制</b>研究](https://file.elecfans.com/web1/M00/48/3E/o4YBAFqnj3CAIz3lAACj-EzDd_w576.jpg)
服務化改造實踐(一)| Dubbo + ZooKeeper
![<b class='flag-5'>服務</b>化改造實踐(一)| <b class='flag-5'>Dubbo</b> + ZooKeeper](https://file.elecfans.com/web1/M00/61/59/pIYBAFuDuCaANVyLAAEeMg_F3gA954.png)
服務化改造實踐(一)| Dubbo + ZooKeeper
![<b class='flag-5'>服務</b>化改造實踐(一)| <b class='flag-5'>Dubbo</b> + ZooKeeper](https://file.elecfans.com/web1/M00/61/5C/pIYBAFuDw4qAFXuaAAEeMg_F3gA989.png)
dubbo-go 中的 TPS Limit 設計與實現
Dubbo 如何成為連接異構微服務體系的最佳服務開發框架
![<b class='flag-5'>Dubbo</b> 如何成為連接異構微<b class='flag-5'>服務</b>體系的最佳<b class='flag-5'>服務</b>開發框架](https://file.elecfans.com/web1/M00/B1/EB/o4YBAF4Fs7OAHo9BAAGm5qZ37VY979.png)
ARM與FPGA的接口實現的解析
![ARM與FPGA的<b class='flag-5'>接口實現</b>的解析](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
C#-Interface接口實現
![C#-Interface<b class='flag-5'>接口實現</b>](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評論