Dubbo之集群容错前导篇(中)

前言


上一篇主要介绍了服务目录 Directory的相关部分,本篇就主要结合源码来说说服务路由 Router相关部分,当然了,这边我们主要关注点是它是如何过滤的。具体的源码分析也能参考官方博客

Router

1.简介

这边可能要联系到Directory的相关内容,回想一下,在上一篇服务目录中RegistryDirectory类中的刷新操作中,其实也需要多次进行路由,目的也就是筛选出符合条件的Invoker。它整个调用的逻辑是这样的

1
notify() --->  refreshInvoker() --->  toInvokers() ---> toMethodInvokers()

其中toMethodInvokers()中也涉及到对router的调用。所以,这两个部分是相互穿插的,具体toMethodInvokers是怎么做的看源码实现就行,这边就不给出了。

2.源码分析

Router类主要有三个实现类,分别是ConditionRouter,MockInvokersSelector,ScriptRouter。这边主要介绍的是ConditionRouter条件路由。

ConditionRouter构造函数

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
public ConditionRouter(URL url) {
this.url = url;
// 获得优先级配置
this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
// 获得是否强制执行配置
this.force = url.getParameter(Constants.FORCE_KEY, false);
try {
// 获得规则
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();
// 解析消费者
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()。方法比较复杂,涉及到用正则表达式去匹配,可以用官网给的例子来说明一下:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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;
// 通过正则表达式匹配路由规则,ROUTE_PATTERN = ([&!=,]*)\s*([^&!=,\s]+)
// 这个表达式看起来不是很好理解,第一个括号内的表达式用于匹配"&", "!", "=" 和 "," 等符号。
// 第二括号内的用于匹配英文字母,数字等字符。举个例子说明一下:
// host = 2.2.2.2 & host != 1.1.1.1 & method = hello
// 匹配结果如下:
// 括号一 括号二
// 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()) {
// 获取括号一内的匹配结果
String separator = matcher.group(1);
// 获取括号二内的匹配结果
String content = matcher.group(2);
// 分隔符为空,表示匹配的是表达式的开始部分
if (separator == null || separator.length() == 0) {
// 创建 MatchPair 对象
pair = new MatchPair();
// 存储 <匹配项, MatchPair> 键值对,比如 <host, MatchPair>
condition.put(content, pair);
}

// 如果分隔符为 &,表明接下来也是一个条件
else if ("&".equals(separator)) {
// 尝试从 condition 获取 MatchPair
if (condition.get(content) == null) {
// 未获取到 MatchPair,重新创建一个,并放入 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;
}

最终condition中的内容是这样的

1
2
3
4
5
6
7
8
9
10
{
"host": {
"matches": ["2.2.2.2"],
"mismatches": ["1.1.1.1"]
},
"method": {
"matches": ["hello"],
"mismatches": []
}
}

route()方法

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
35
36
@Override
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
throws RpcException {
// 为空,直接返回空 Invoker 集合
if (invokers == null || invokers.isEmpty()) {
return invokers;
}
try {
// 如果不匹配 `whenCondition` ,直接返回 `invokers` 集合,因为不需要走 `whenThen` 的匹配
if (!matchWhen(url, invocation)) {
return invokers;
}
List<Invoker<T>> result = new ArrayList<Invoker<T>>();
// 如果thenCondition为空,则直接返回空
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) {
// 如果thenCondition匹配,则加入result
if (matchThen(invoker.getUrl(), url)) {
result.add(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(Constants.RULE_KEY));
return result;
}
} catch (Throwable t) {
logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
}
return invokers;
}

所以总结来说,当我们配置了禁用的服务提供者后,根据路由信息就不会把它对应的invokers给添加进来,实现了过滤的目的。