Dubbo 路由機制是在服務間的調用時,經過將服務提供者按照設定的路由規則來決定調用哪個具體的服務。java
Dubbo 實現路由都是經過實現 RouterFactory 接口。當前版本 dubbo-2.7.5 實現該接口類以下:ide
路由實現工廠類是在 router 包下函數
因爲 RouterFactory 是 SPI 接口,同時在獲取路由 RouterFactory#getRouter 方法上有 @Adaptive("protocol") 註解,因此在獲取路由的時候會動態調用須要的工廠類。this
能夠看到 getRouter 方法返回的是一個 Router 接口,該接口信息以下url
其中 Router#route 是服務路由的入口,對於不一樣類型的路由工廠,有特定的 Router 實現類。spa
以上就是經過解析 URL,獲取到具體的 Router,經過調用 Router#router 過濾出符合當前路由規則的 invokers。3d
上面展現了路由實現類,這幾個實現類型中,ConditionRouter 條件路由是最爲經常使用的類型,因爲文章篇幅有限,本文就不對所有的路由類型逐一分析,只對條件路由進行具體分析,只要弄懂這一個類型,其它類型的解析就能容易掌握。code
在分析條件路由前,先了解條件路由的參數配置,官方文檔以下:router
條件路由規則內容以下:對象
分析路由實現,主要分析工廠類的 xxxRouterFactory#getRouter 和 xxxRouter#route 方法。
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 就是保存的是條件變量值。
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 上能夠設置路由,你們能夠嘗試各類使用規則,經過實操才能更好掌握和理解路由機制的實現。
我的博客: https://ytao.top
關注公衆號 【ytao】,更多原創好文