集群容错(七)之Router实现
本文基于 Dubbo 2.6.1 版本,望知悉。
1. 概述
本文接 《精尽 Dubbo 源码解析 —— 集群容错(六)之 Configurator 实现》 一文,分享 dubbo-cluster 模块, router 包,实现 Dubbo 的路由规则功能。
Router 相关类,如下图:
Router 相关类
老艿艿:本文对应 《Dubbo 用户指南 —— 路由规则》 文档。如果之前没了解过该功能的胖友,请先阅读了解下哈。
2. RouterFactory
com.alibaba.dubbo.rpc.cluster.RouterFactory ,Router 工厂接口。代码如下:
```plain text plain @SPI public interface RouterFactory { /** * Create router. * * 创建 Router 对象 * * @param url * @return router */ @Adaptive(“protocol”) Router getRouter(URL url); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---
- @SPI**拓展点**
注解,Dubbo SPI
,无默认值。
- @Adaptive(“protocol”)
注解,基于 Dubbo SPI Adaptive 机制,加载对应的 Router 实现,使用
URL.protocol
属性。
- #getRouter(URL url)
接口方法,获得 Router 对象。
## 2.1 ConditionRouterFactory
com.alibaba.dubbo.rpc.cluster.router.condition.ConditionRouterFactory ,实现 RouterFactory 接口,**ConditionRouter** 工厂实现类。代码如下:
```plain text
plain public class ConditionRouterFactory implements RouterFactory { public static final String NAME = "condition"; @Override public Router getRouter(URL url) { return new ConditionRouter(url); } }
- 对应 Router 实现类为 ConditionRouter 。
2.2 ScriptRouterFactory
com.alibaba.dubbo.rpc.cluster.router.script.ScriptRouterFactory ,实现 RouterFactory 接口,ScriptRouter 工厂实现类。代码如下:
```plain text plain public class ScriptRouterFactory implements RouterFactory { public static final String NAME = “script”; @Override public Router getRouter(URL url) { return new ScriptRouter(url); } }
1
2
3
4
5
6
7
8
9
10
11
---
- 对应 Router 实现类为 ScriptRouter 。
## 2.3 FileRouterFactory
com.alibaba.dubbo.rpc.cluster.router.file.FileRouterFactory ,实现 RouterFactory 接口,基于**文件**读取路由规则,创建**对应的 Router 实现类的对象**。代码如下:
```plain text
plain 1: public class FileRouterFactory implements RouterFactory { 2: 3: public static final String NAME = "file"; 4: 5: /** 6: * RouterFactory$Adaptive 对象 7: */ 8: private RouterFactory routerFactory; 9: 10: public void setRouterFactory(RouterFactory routerFactory) { 11: this.routerFactory = routerFactory; 12: } 13: 14: @Override 15: public Router getRouter(URL url) { 16: try { 17: // Transform File URL into Script Route URL, and Load 18: // file:///d:/path/to/route.js?router=script ==> script:///d:/path/to/route.js?type=js&rule=<file-content> 19: // 获得 router 配置项,默认为 script 20: String protocol = url.getParameter(Constants.ROUTER_KEY, ScriptRouterFactory.NAME); // Replace original protocol (maybe 'file') with 'script' 21: // 使用文件后缀做为类型 22: String type = null; // Use file suffix to config script type, e.g., js, groovy ... 23: String path = url.getPath(); 24: if (path != null) { 25: int i = path.lastIndexOf('.'); 26: if (i > 0) { 27: type = path.substring(i + 1); 28: } 29: } 30: // 读取规则内容 31: String rule = IOUtils.read(new FileReader(new File(url.getAbsolutePath()))); 32: 33: // 创建路由规则 URL 34: boolean runtime = url.getParameter(Constants.RUNTIME_KEY, false); 35: URL script = url.setProtocol(protocol).addParameter(Constants.TYPE_KEY, type) 36: .addParameter(Constants.RUNTIME_KEY, runtime) 37: .addParameterAndEncoded(Constants.RULE_KEY, rule); 38: 39: // 通过 Dubbo SPI Adaptive 机制,获得 Router 对象 40: return routerFactory.getRouter(script); 41: } catch (IOException e) { 42: throw new IllegalStateException(e.getMessage(), e); 43: } 44: } 45: 46: }
- 第 20 行:获得 “router” 配置项,默认为 “script” 。
- 第 21 至 29 行:获得类型 ,基于文件后缀。
- 第 31 行:从文件规则内容 中,读取 。
- 第 33 至 37 行:创建路由规则 URL 对象。
- 第 40 行:通过 Dubbo SPI Adaptive对应的 Router 对象 机制,获得 。
3. Router
com.alibaba.dubbo.rpc.cluster.Router ,实现 Comparable 接口,路由规则接口。代码如下:
```plain text plain public interface Router extends Comparable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---
- **一个 Router 对象,对应一条路由规则**
。
- Configurator 有**优先级**
的要求,所以实现 Comparable 接口。
- #getUrl()
接口方法,获得路由 URL ,里面带有路由规则。
- #route(List<Invoker> invokers, URL url, Invocation invocation)**匹配的**
接口方法,路由,筛选
Invoker 集合。
## 3.1 ConditionRouter
com.alibaba.dubbo.rpc.cluster.router.condition.ConditionRouter ,实现 Router 接口,**基于条件表达式**的 Router 实现类。
基于条件表达式的路由规则,如:host = 10.20.153.10 => host = 10.20.153.11
**注意**,胖友一定要看了 [《Dubbo 用户指南 —— 路由规则》](http://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule.html#%E6%9D%A1%E4%BB%B6%E8%B7%AF%E7%94%B1%E8%A7%84%E5%88%99) 的 [条件路由规则](http://svip.iocoder.cn/Dubbo/cluster-7-impl-router/#) 部分,不然下面影响理解。
### 3.1.1 构造方法
```plain text
plain /** * 分组正则匹配,详细见 {@link #parseRule(String)} 方法 * * 前 [] 为匹配,分隔符 * 后 [] 为匹配,内容 */ private static Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)"); /** * 路由规则 URL */ private final URL url; /** * 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0 。 */ private final int priority; /** * 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false 。 */ private final boolean force; /** * 消费者匹配条件集合,通过解析【条件表达式 rule 的 `=>` 之前半部分】 */ private final Map<String, MatchPair> whenCondition; /** * 提供者地址列表的过滤条件,通过解析【条件表达式 rule 的 `=>` 之后半部分】 */ private final Map<String, MatchPair> thenCondition; public ConditionRouter(URL url) { this.url = url; this.priority = url.getParameter(Constants.PRIORITY_KEY, 0); this.force = url.getParameter(Constants.FORCE_KEY, false); try { // 拆分条件变大时为 when 和 then 两部分 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("=>"); String whenRule = i < 0 ? null : rule.substring(0, i).trim(); String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim(); // 解析 `whenCondition` Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule); // 解析 `thenCondition` 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); } }
- 每个字段的解释,胖友自己看下注释。
- MatchPair ,见 「3.1.2 MatchPair」 中。
- #parseRule()「3.1.3 parseRule」 方法,见 中。
3.1.2 MatchPair
MatchPair 为 ConditionRouter 的内部静态类,用于匹配的值组。每个属性条件,例如 methodhost 等,对应一个 MatchPair 对象。代码如下:
```plain text plain private static final class MatchPair { /** * 匹配的值集合 */ final Set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---
- #isMatch(String value, URL param)**匹配**
方法,判断
value
是否
matches
和
mismatches
。
- 那么为什么会有
param
参数呢?因为要支持
$
从 URL 中,读取参数。
- #UrlUtils#isMatchGlobPattern(match, value, URL)
方法,支持
-
通配,判断
match
和
value
是否匹配。代码如下:
```plain text
plain public static boolean isMatchGlobPattern(String pattern, String value, URL param) { // 以美元符 `$` 开头,表示引用参数 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 ((pattern == null || pattern.length() == 0) && (value == null || value.length() == 0)) { return true; } // 有一个为空,不匹配 if ((pattern == null || pattern.length() == 0) || (value == null || value.length() == 0)) { return false; } // 支持 * 的通配 int i = pattern.lastIndexOf('*'); // doesn't find "*" if (i == -1) { return value.equals(pattern); } // "*" is at the end else if (i == pattern.length() - 1) { return value.startsWith(pattern.substring(0, i)); } // "*" is at the beginning else if (i == 0) { return value.endsWith(pattern.substring(i + 1)); } // "*" is in the middle else { String prefix = pattern.substring(0, i); String suffix = pattern.substring(i + 1); return value.startsWith(prefix) && value.endsWith(suffix); } }
```plain text * x
1
2
3
4
5
6
7
8
9
代码比较简单,所以胖友自己读下。
### 3.1.3 parseRule
#parseRule(rule) 方法,解析路由配置内容 “rule” 。代码如下:
```plain text
plain 1: private static Map<String, MatchPair> parseRule(String rule) throws ParseException { 2: // System.out.println("rule: " + rule); // add by 芋艿,方便大家看 3: Map<String, MatchPair> condition = new HashMap<String, MatchPair>(); 4: if (StringUtils.isBlank(rule)) { 5: return condition; 6: } 7: // Key-Value pair, stores both match and mismatch conditions 8: MatchPair pair = null; 9: // Multiple values 10: Set<String> values = null; 11: final Matcher matcher = ROUTE_PATTERN.matcher(rule); 12: while (matcher.find()) { // Try to match one by one 13: String separator = matcher.group(1); 14: String content = matcher.group(2); 15: // System.out.println(separator + "\t" + content); // add by 芋艿,方便大家看 16: // Start part of the condition expression. 17: if (separator == null || separator.length() == 0) { 18: pair = new MatchPair(); 19: condition.put(content, pair); 20: } 21: // The KV part of the condition expression 22: else if ("&".equals(separator)) { 23: if (condition.get(content) == null) { 24: pair = new MatchPair(); 25: condition.put(content, pair); 26: } else { 27: pair = condition.get(content); 28: } 29: } 30: // The Value in the KV part. 31: else if ("=".equals(separator)) { 32: if (pair == null) { 33: throw new ParseException("Illegal route rule \"" + rule + "\", The error char '" + separator + "' at index " + matcher.start() + " before \"" + content + "\".", matcher.start()); 34: } 35: values = pair.matches; 36: values.add(content); 37: } 38: // The Value in the KV part. 39: else if ("!=".equals(separator)) { 40: if (pair == null) { 41: throw new ParseException("Illegal route rule \"" + rule + "\", The error char '" + separator + "' at index " + matcher.start() + " before \"" + content + "\".", matcher.start()); 42: } 43: values = pair.mismatches; 44: values.add(content); 45: } 46: // The Value in the KV part, if Value have more than one items. 47: else if (",".equals(separator)) { // Should be seperateed by ',' 48: if (values == null || values.isEmpty()) { 49: throw new ParseException("Illegal route rule \"" + rule + "\", The error char '" + separator + "' at index " + matcher.start() + " before \"" + content + "\".", matcher.start()); 50: } 51: values.add(content); 52: } else { 53: throw new ParseException("Illegal route rule \"" + rule + "\", The error char '" + separator + "' at index " + matcher.start() + " before \"" + content + "\".", matcher.start()); 54: } 55: } 56: return condition; 57: }
- 第 11 至 14 行:通过 循环 ROUTE_PATTERN 正则匹配 rule , 多次,直到结束。如下是两个例子:
```plain text plain rule: host = 192.168.3.17 & method = say01 host = 192.168.3.17 & method = say01 ———- 分割线 ———- rule: host = 192.168.3.17 host = 192.168.3.17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
---
- 第 16 至 29 行:处理条件**属性**
的情况,例如:
host
和
& method
等等,此时会获得对应的 MatchPair 对象。若不存在,则进行创建 MatchPair 对象。
- 第 30 至 45 行:处理条件**条件值**
的情况,例如:
= 192.168.3.17
和
!= say01
等等,此时会添加到 MatchPair 的
matches
或
mismatches
中。
- 第 46 至 51 行:处理条件**条件值也会**
以逗号(
,
)分隔多个值的情况,此时
添加到 MatchPair 的
matches
或
mismatches
中。
- 第 52 至 54 行:非法,抛出 ParseException 异常。
### 3.1.4 route
```plain text
plain 1: @Override 2: public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { 3: // 为空,直接返回空 Invoker 集合 4: if (invokers == null || invokers.isEmpty()) { 5: return invokers; 6: } 7: try { 8: // 不匹配 `whenCondition` ,直接返回 `invokers` 集合,因为不需要走 `whenThen` 的匹配 9: if (!matchWhen(url, invocation)) { 10: return invokers; 11: } 12: List<Invoker<T>> result = new ArrayList<Invoker<T>>(); 13: // `whenThen` 为空,则返回空 Invoker 集合 14: if (thenCondition == null) { 15: logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey()); 16: return result; 17: } 18: // 使用 `whenThen` ,匹配 `invokers` 集合。若符合,添加到 `result` 中 19: for (Invoker<T> invoker : invokers) { 20: if (matchThen(invoker.getUrl(), url)) { 21: result.add(invoker); 22: } 23: } 24: // 若 `result` 非空,返回它 25: if (!result.isEmpty()) { 26: return result; 27: // 如果 `force=true` ,代表强制执行,返回空 Invoker 集合 28: } else if (force) { 29: logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(Constants.RULE_KEY)); 30: return result; 31: } 32: } catch (Throwable t) { 33: logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t); 34: } 35: // 如果 `force=false` ,代表不强制执行,返回 `invokers` 集合,即忽略路由规则 36: return invokers; 37: }
- 第 3 至 6 行:若 空 invokers 为空,直接返回 Invoker 集合。
- 第 8 至 11 行:调用 消费者 #matchWhen(url, invocation) 方法,使用服务 url 匹配 whenCondition 。代码如下:
```plain text plain boolean matchWhen(URL url, Invocation invocation) { return whenCondition == null || whenCondition.isEmpty() || matchCondition(whenCondition, url, null, invocation); }
1
2
3
4
5
6
7
---
```plain text
- <font style="color:rgb(51, 51, 51);">如果匹配条件为空,表示对所有消费方应用,如:</font><font style="color:rgb(51, 51, 51);">=> host != 10.20.153.11</font><font style="color:rgb(51, 51, 51);"> 。</font>
- <font style="color:rgb(51, 51, 51);">若</font>**<font style="color:rgb(51, 51, 51);">不匹配</font>**<font style="color:rgb(51, 51, 51);">,则直接返回</font>**<font style="color:rgb(51, 51, 51);">全</font>**<font style="color:rgb(51, 51, 51);">invokers</font><font style="color:rgb(51, 51, 51);"> 集合,因为不需要走 </font><font style="color:rgb(51, 51, 51);">whenThen</font><font style="color:rgb(51, 51, 51);"> 的匹配。</font>
- <font style="color:rgb(51, 51, 51);">#matchCondition(...)</font><font style="color:rgb(51, 51, 51);"> 方法的详细解析,见 </font>[「3.1.5 matchCondition」](http://svip.iocoder.cn/Dubbo/cluster-7-impl-router/#)<font style="color:rgb(51, 51, 51);"> 。</font>
- 第 13 至 17 行:若 空 whenThen 为空,则返回 Invoker 集合。
- 第 18 至 23 行:循环提供者者 调用 #matchThen(url, invocation) 方法,使用服务 invokers 的 URL ,匹配 whenThen 集合。代码如下:
```plain text plain private boolean matchThen(URL url, URL param) { return !(thenCondition == null || thenCondition.isEmpty()) && matchCondition(thenCondition, url, param, null); }
1
2
3
4
5
6
---
```plain text
- <font style="color:rgb(51, 51, 51);">如果过滤条件为空,表示禁止访问,如:</font><font style="color:rgb(51, 51, 51);">host = 10.20.153.10 =></font><font style="color:rgb(51, 51, 51);"> 。</font>
- <font style="color:rgb(51, 51, 51);">若</font>**<font style="color:rgb(51, 51, 51);">匹配</font>**<font style="color:rgb(51, 51, 51);">,添加到 </font><font style="color:rgb(51, 51, 51);">result</font><font style="color:rgb(51, 51, 51);"> 中。</font>
- ========== 处理 三种 result + force 的 情况 ==========
- 第 24 至 26 行:若 非空它 result ,返回 。
- 第 27 至 31 行:若 为空空 result ,如果 force=true ,代表强制执行,返回 Invoker 集合。
- 第 36 行:若 为空不全忽略 result ,如果 force=false ,代表 强制执行,返回 invokers 集合,即 路由规则。
情况比较多,胖友可以回过头在理一理。
3.1.5 matchCondition
```plain text plain private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) { Map<String, String> sample = url.toMap(); boolean result = false; // 是否匹配 for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) { // 获得条件属性 String key = matchPair.getKey(); String sampleValue; // get real invoked method name from invocation if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) { sampleValue = invocation.getMethodName(); } else { sampleValue = sample.get(key); if (sampleValue == null) { sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key); } } // 匹配条件值 if (sampleValue != null) { if (!matchPair.getValue().isMatch(sampleValue, param)) { // 返回不匹配 return false; } else { result = true; } } else { // not pass the condition if (!matchPair.getValue().matches.isEmpty()) { // 无条件值,但是有匹配条件 matches ,则返回不匹配。 return false; } else { result = true; } } } return result; }
1
2
3
4
5
6
7
---
### 3.1.5 compareTo
```plain text
plain @Override public int compareTo(Router o) { if (o == null || o.getClass() != ConditionRouter.class) { return 1; } ConditionRouter c = (ConditionRouter) o; return this.priority == c.priority ? url.toFullString().compareTo(c.url.toFullString()) : (this.priority > c.priority ? 1 : -1); }
- 优先,按照 降序 “priority” 。
- 其次,按照 升序 “url” 。
3.2 ScriptRouter
com.alibaba.dubbo.rpc.cluster.router.script.ScriptRouter ,实现 Router 接口,基于脚本的 Router 实现类。
脚本路由规则 4 支持 JDK 脚本引擎的所有脚本,比如:javascript, jruby, groovy 等,通过 type=javascript 参数设置脚本类型,缺省为 javascript。
```plain text plain > “script://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=” + URL.encode(“function route(invokers) { … } (invokers)”) >
1
2
3
4
5
6
7
---
基于脚本引擎的路由规则,如:
```plain text
plain > function route(invokers) { > var result = new java.util.ArrayList(invokers.size()); > for (i = 0; i < invokers.size(); i ++) { > if ("10.20.153.10".equals(invokers.get(i).getUrl().getHost())) { > result.add(invokers.get(i)); > } > } > return result; > } (invokers); // 表示立即执行方法 >
3.2.1 构造方法
```plain text plain /** * 脚本类型 与 ScriptEngine 的映射缓存 */ private static final Map<String, ScriptEngine> engines = new ConcurrentHashMap<String, ScriptEngine>(); /** * 路由规则 URL */ private final ScriptEngine engine; /** * 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0 。 */ private final int priority; /** * 路由规则内容 */ private final String rule; /** * 路由规则 URL */ private final URL url; public ScriptRouter(URL url) { this.url = url; String type = url.getParameter(Constants.TYPE_KEY); this.priority = url.getParameter(Constants.PRIORITY_KEY, 0); String rule = url.getParameterAndDecoded(Constants.RULE_KEY); // 初始化 engine if (type == null || type.length() == 0) { type = Constants.DEFAULT_SCRIPT_TYPE_KEY; } if (rule == null || rule.length() == 0) { throw new IllegalStateException(new IllegalStateException(“route rule can not be empty. rule:” + rule)); } ScriptEngine engine = engines.get(type); if (engine == null) { // 在缓存中不存在,则进行创建 ScriptEngine 对象 engine = new ScriptEngineManager().getEngineByName(type); if (engine == null) { throw new IllegalStateException(new IllegalStateException(“Unsupported route rule type: “ + type + “, rule: “ + rule)); } engines.put(type, engine); } this.engine = engine; this.rule = rule; }
1
2
3
4
5
6
7
---
### 3.2.2 route
```plain text
plain @Override public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { try { // 执行脚本 List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers); Compilable compilable = (Compilable) engine; Bindings bindings = engine.createBindings(); bindings.put("invokers", invokersCopy); bindings.put("invocation", invocation); bindings.put("context", RpcContext.getContext()); CompiledScript function = compilable.compile(rule); // 编译 Object obj = function.eval(bindings); // 执行 // 根据结果类型,转换成 (List<Invoker<T>> 类型返回 if (obj instanceof Invoker[]) { invokersCopy = Arrays.asList((Invoker<T>[]) obj); } else if (obj instanceof Object[]) { invokersCopy = new ArrayList<Invoker<T>>(); for (Object inv : (Object[]) obj) { invokersCopy.add((Invoker<T>) inv); } } else { invokersCopy = (List<Invoker<T>>) obj; } return invokersCopy; } catch (ScriptException e) { // 发生异常,忽略路由规则,返回全 `invokers` 集合 logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e); return invokers; } }
- 比较易懂,胖友自己看代码注释。
3.2.3 compareTo
```plain text plain @Override public int compareTo(Router o) { if (o == null || o.getClass() != ScriptRouter.class) { return 1; } ScriptRouter c = (ScriptRouter) o; return this.priority == c.priority ? rule.compareTo(c.rule) : (this.priority > c.priority ? 1 : -1); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
---
- 优先,按照 **降序**
“priority”
。
- 其次,按照 **升序**
“rule”
。
## 3.3 MockInvokersSelector
详细解析,见 [《精尽 Dubbo 源码解析 —— 集群容错(八)之 Mock 实现》](http://svip.iocoder.cn/Dubbo/cluster-8-impl-mock/?self=) 。
# 4. 集成 Router 模块
如下图所示,我们可以看到,有**二个类**,调用 Router#route(List<Invoker>, URL, Invocation) 方法,集成 Router 模块。

集成
## 4.1 AbstractDirectory
### 4.1.1 setRouters
#setRouters(List routers) 方法,设置路由规则们。代码如下:
```plain text
plain 1: protected void setRouters(List<Router> routers) { 2: // copy list // 复制 routers ,因为下面要修改 3: routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers); 4: // append url router 5: // 拼接 `url` 中,配置的路由规则 6: String routerkey = url.getParameter(Constants.ROUTER_KEY); 7: if (routerkey != null && routerkey.length() > 0) { 8: RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey); 9: routers.add(routerFactory.getRouter(url)); 10: } 11: // append mock invoker selector 12: routers.add(new MockInvokersSelector()); 13: // 排序 14: Collections.sort(routers); 15: // 赋值给属性 16: this.routers = routers; 17: }
- 第 3 行:复制重新创建 routers 数组,因为下面会进行修改。
- 第 5 至 10 行:添加配置的路由规则 url 中 到 routers 中。例如:
```plain text plain
1
2
3
4
5
---
```plain text
- <font style="color:rgb(51, 51, 51);">受限于 XML 对字符的限制,</font><font style="color:rgb(51, 51, 51);">"condition"</font><font style="color:rgb(51, 51, 51);"> 或 </font><font style="color:rgb(51, 51, 51);">"script"</font><font style="color:rgb(51, 51, 51);"> 类型的路由配置会比较难设置。所以笔者认为,如果是使用 XML 配置路由规则,</font><font style="color:rgb(51, 51, 51);">"file"</font><font style="color:rgb(51, 51, 51);"> 类型是比较合适的方式。当然,如果使用 Java API 又或者注解的方式,应该不存在这样的问题。</font>
- 第 12 行:添加 MockInvokersSelector 到 routers 中。
- 第 14 行:排序 routers 。
- 第 16 行:赋值属性 给 AbstractDirectory 。
4.1.2 list
```plain text plain 1: @Override 2: public List<Invoker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
- 第 8 至 20 行:**循环匹配的**
,调用
Router#route(invokers, url, invocation)
方法,不断路由,筛选
Invoker 集合。
- 第 13 行:判断 **true否则只在提供者地址列表变更时预先执行并缓存结果**
“runtime”
为
才执行:是否在每次调用时执行路由规则,
,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为
true
,需要注意设置会影响调用的性能,可不填,缺省为
flase
。
## 4.2 RegistryDirectory
### 4.2.1 notify
```plain text
plain 1: @Override 2: public synchronized void notify(List<URL> urls) { 3: // 【省略无关代码】根据 URL 的分类或协议,分组成三个集合 。 4: List<URL> invokerUrls = new ArrayList<URL>(); // 服务提供者 URL 集合 5: List<URL> routerUrls = new ArrayList<URL>(); 6: List<URL> configuratorUrls = new ArrayList<URL>(); 7: 8: // 【省略无关代码】处理配置规则 URL 集合 9: // configurators 10: 11: // 处理路由规则 URL 集合 12: if (!routerUrls.isEmpty()) { 13: List<Router> routers = toRouters(routerUrls); 14: if (routers != null) { // null - do nothing 15: setRouters(routers); 16: } 17: } 18: 19: // 【省略无关代码】合并配置规则,到 `directoryUrl` 中,形成 `overrideDirectoryUrl` 变量。 20: // 【省略无关代码】处理服务提供者 URL 集合 21: }
- 第 12 行:若注册中心通知的 routerUrls 非空,进行处理 routerUrls 集合。
- 第 13 行:调用 转换 #toRouters(routerUrls) 方法,将路由规则 URL 集合, 成对应的 Router 集合。代码如下:
```plain text plain private List
1
2
3
4
5
---
```plain text
- <font style="color:rgb(51, 51, 51);">代码易懂,胖友看下注释理解。</font>
- 第 14 至 16 行:null0「4.1.1 setRouters」 routers 集合非 ( 允许集合大小为 ),调用 #setRouters(routers) 方法,设置路由规则集合,即 。
4.2.2 toMethodInvokers
```plain text plain 1: private Map<String, List<Invoker
1
2
3
4
5
6
7
8
9
10
11
12
13
---
- 第 8 行:调用 **全进行缓存***只在提供者地址列表变更时预先执行并缓存结果*
#route(invokers, method)
方法,路由
invokersList
,匹配合适的 Invoker 集合
,这就是上文提到的“
”。代码如下:
```plain text
plain private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) { // 创建 Invocation 对象 Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]); // 获得 Router 数组 List<Router> routers = getRouters(); // 根据路由规则,筛选 Invoker 集合 if (routers != null) { for (Router router : routers) { if (router.getUrl() != null) { invokers = router.route(invokers, getConsumerUrl(), invocation); } } } return invokers; }
```plain text
- 主要是调用 Router#route(...) 方法,路由。
```
- 第 11 至 20 行:循环每个方法进行缓存 ,调用 #route(invokers, method) 方法,路由 的 methodInvokers ,匹配合适的 Invoker 集合 。
