目录
dubbo拓展机制 SPI
dubbo 自适应拓展机制
dubbo 服务导出
dubbo 服务引用
dubbo 服务字典
dubbo 服务路由
dubbo 集群
dubbo 负载均衡
dubbo 服务调用过程
上一篇文章分析了集群容错的第一部分 — 服务目录 Directory。服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者。在详细分析服务路由的源码之前,先来介绍一下服务路由是什么。服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。其中条件路由是我们最常使用的,标签路由是一个新的实现,暂时还未发布,该实现预计会在 2.7.x 版本中发布。本篇文章将分析条件路由相关源码,脚本路由和标签路由这里就不分析了。
1表达式解析
条件路由规则是一条字符串,对于 Dubbo 来说,它并不能直接理解字符串的意思,需要将其解析成内部格式才行。条件表达式的解析过程始于 ConditionRouter 的构造方法,下面一起看一下
public ConditionRouter(URL url) {
this.url = url;
// 获取 priority,路由规则的优先级,用于排序,优先级越大越靠前执行
this.priority = url.getParameter(PRIORITY_KEY, 0);
// false,不调用mock服务。
// true,当服务调用失败时,使用mock服务。
// default,当服务调用失败时,使用mock服务。
// force,强制使用Mock服务(不管服务能否调用成功)。(使用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("=>");
// 分别获取服务消费者和提供者匹配规则
String whenRule = i < 0 ? null : rule.substring(0, i).trim();
String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
// 解析服务消费者匹配规则
Map when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap() : parseRule(whenRule);
// 解析服务提供者匹配规则
Map 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 class MatchPair {
// 存放匹配条件
final Set matches = new HashSet();
// 存放不匹配的条件
final Set mismatches = new HashSet();
}
继续看parseRule方法
private static Map parseRule(String rule)
throws ParseException {
// 定义条件映射集合
Map condition = new HashMap();
if (StringUtils.isBlank(rule)) {
return condition;
}
// Key-Value pair, stores both match and mismatch conditions
MatchPair pair = null;
// Multiple values
Set 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()) { // Try to match one by one
// 获取括号一内的匹配结果
String separator = matcher.group(1);
// 获取括号二内的匹配结果
String content = matcher.group(2);
// Start part of the condition expression.
// 分隔符为空,表示匹配的是表达式的开始部分
if (StringUtils.isEmpty(separator)) {
pair = new MatchPair();
condition.put(content, pair);
}
// The KV part of the condition expression
// 如果分隔符为 &,表明接下来也是一个条件
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将被解析为
2服务路由
服务路由的入口方法是 ConditionRouter 的 router 方法,该方法定义在 Router 接口中。实现代码如下:
public List> route(List> invokers, URL url, Invocation invocation)
throws RpcException {
if (!enabled) {
return invokers;
}
if (CollectionUtils.isEmpty(invokers)) {
return invokers;
}
try {
// 先对服务消费者条件进行匹配,如果匹配失败,表明服务消费者 url 不符合匹配规则,
// 无需进行后续匹配,直接返回 Invoker 列表即可。比如下面的规则:
// host = 10.20.153.10 => host = 10.0.0.10
// 这条路由规则希望 IP 为 10.20.153.10 的服务消费者调用 IP 为 10.0.0.10 机器上的服务。
// 当消费者 ip 为 10.20.153.11 时,matchWhen 返回 false,表明当前这条路由规则不适用于
// 当前的服务消费者,此时无需再进行后续匹配,直接返回即可。
if (!matchWhen(url, invocation)) {
return invokers;
}
List> result = new ArrayList>();
// 服务提供者匹配条件未配置,表明对指定的服务消费者禁用服务,也就是服务消费者在黑名单中
if (thenCondition == null) {
logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
return result;
}
// 这里可以简单的把 Invoker 理解为服务提供者,现在使用服务提供者匹配规则对
// Invoker 列表进行匹配
for (Invoker invoker : invokers) {
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
// 返回匹配结果,如果 result 为空列表,且 force = true,表示强制返回空列表,
// 否则路由结果为空的路由规则将自动失效
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 方法先是调用 matchWhen 对服务消费者进行匹配,如果匹配失败,直接返回 Invoker 列表。如果匹配成功,再对服务提供者进行匹配,匹配逻辑封装在了 matchThen 方法中。下面来看一下这两个方法的逻辑:
boolean matchWhen(URL url, Invocation invocation) {
// 服务消费者条件为 null 或空,均返回 true,比如:
// => host != 172.22.3.91
// 表示所有的服务消费者都不得调用 IP 为 172.22.3.91 的机器上的服务
return CollectionUtils.isEmptyMap(whenCondition) || matchCondition(whenCondition, url, null, invocation);
}
private boolean matchThen(URL url, URL param) {
// 服务提供者条件为 null 或空,表示禁用服务
return CollectionUtils.isNotEmptyMap(thenCondition) && matchCondition(thenCondition, url, param, null);
}
继续看matchCondition
private boolean matchCondition(Map condition, URL url, URL param, Invocation invocation) {
// 将服务提供者或消费者 url 转成 Map
Map sample = url.toMap();
boolean result = false;
// 遍历 condition 列表
for (Map.Entry matchPair : condition.entrySet()) {
// 获取匹配项名称,比如 host、method 等
String key = matchPair.getKey();
String sampleValue;
//get real invoked method name from invocation
// 如果 invocation 不为空,且 key 为 mehtod(s),表示进行方法匹配
if (invocation != null && (METHOD_KEY.equals(key) || METHODS_KEY.equals(key))) {
// 从 invocation 获取被调用方法的名称
sampleValue = invocation.getMethodName();
} else if (ADDRESS_KEY.equals(key)) {
// 获取服务提供者或是服务消费者的地址
sampleValue = url.getAddress();
} else if (HOST_KEY.equals(key)) {
// 获取服务提供者或是服务消费者的host
sampleValue = url.getHost();
} else {
// 从服务提供者或消费者 url 中获取指定字段值,比如 application 等
sampleValue = sample.get(key);
if (sampleValue == null) {
// 尝试通过 default.xxx 获取相应的值
sampleValue = sample.get(DEFAULT_KEY_PREFIX + key);
}
}
if (sampleValue != null) {
// 调用 MatchPair 的 isMatch 方法进行匹配
if (!matchPair.getValue().isMatch(sampleValue, param)) {
// 只要有一个规则匹配失败,立即返回 false 结束方法逻辑
return false;
} else {
result = true;
}
} else {
//not pass the condition
// sampleValue 为空,表明服务提供者或消费者 url 中不包含相关字段。此时如果
// MatchPair 的 matches 不为空,表示匹配失败,返回 false。比如我们有这样
// 一条匹配条件 loadbalance = random,假设 url 中并不包含 loadbalance 参数,
// 此时 sampleValue = null。既然路由规则里限制了 loadbalance 必须为 random,
// 但 sampleValue = null,明显不符合规则,因此返回 false
if (!matchPair.getValue().matches.isEmpty()) {
return false;
} else {
result = true;
}
}
}
return result;
}
主要的匹配规则都在isMatch中
private boolean isMatch(String value, URL param) {
// 情况一:matches 非空,mismatches 为空
if (!matches.isEmpty() && mismatches.isEmpty()) {
for (String match : matches) {
// 遍历 matches 集合,检测入参 value 是否能被 matches 集合元素匹配到。
// 举个例子,如果 value = 10.20.153.11,matches = [10.20.153.*],
// 此时 isMatchGlobPattern 方法返回 true
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
return true;
}
}
// 如果所有匹配项都无法匹配到入参,则返回 false
return false;
}
// 情况二:matches 为空,mismatches 非空
if (!mismatches.isEmpty() && matches.isEmpty()) {
for (String mismatch : mismatches) {
// 只要入参被 mismatches 集合中的任意一个元素匹配到,就返回 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 均为非空,此时优先使用 mismatches 集合元素对入参进行匹配。
// 只要 mismatches 集合中任意一个元素与入参匹配成功,就立即返回 false,结束方法逻辑
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
return false;
}
}
// mismatches 集合元素无法匹配到入参,此时再使用 matches 继续匹配
for (String match : matches) {
// 只要 matches 集合中任意一个元素与入参匹配成功,就立即返回 true
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
return true;
}
}
// 全部失配,则返回 false
return false;
}
// 情况四:matches 和 mismatches 均为空,此时返回 false
return false;
}