Dubbo进阶(十四):Router的实现

首先我们依然再看一遍Dubbo调用的流程

在这里插入图片描述

Router是什么

Router是一种约定的规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。比如服务A配置了调用的服务的IP是192.168.1.1,那么路由会过滤到除192.168.1.1之外的所有的服务,只会返回192.168.1.1

通常服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者。

在Dubbo 2.7.x的版本中,路由包含了条件路由、脚本路由、文件路由和标签路由四种路由规则。

在这里插入图片描述

本文以ConditionRouter条件路由为例,分析一下对应的规则和源码。

ConditionRouter条件路由的规则

参数名称含义
condition://表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填。
0.0.0.0表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填。
com.foo.BarService表示只对指定服务生效,必填。
category=routers表示该数据为动态配置类型,必填。
dynamic=false表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填。
enabled=true覆盖规则是否生效,可不填,缺省生效。
force=false当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 flase。
runtime=false是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 flase。
priority=1路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0。
rule=URL.encode("host = 10.20.153.10 => host = 10.20.153.11")表示路由规则的内容,必填。

以上就是ConditionRouter条件路由的一些规则。

比如有这样一条规则:host = 10.20.153.10 => host = 10.20.153.11,该条规则表示 IP 为 10.20.153.10 的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下:[服务消费者匹配条件] => [服务提供者匹配条件]

  • =>之前的部分为消费者匹配条件,向所有的参数和消费者的URL进行对比,当消费者满足匹配条件时,对该消费者执行后面的过滤规则。

  • =>之后的部分为提供者地址列表的过滤条件,将所有参数和URL进行对比,消费者最终只会去过滤后的地址列表。

  • 如果匹配条件为空,则表示应用于所有消费方,如=> host ! = 10.20.153.11
  • 如果过滤条件为空,则表示禁止访问,如 host = 10.20.153.11 =>

接下来分析一下表达式解析的源码

这里是一个工厂模式,通过 routerFactory.getRouter(url)会创建一个 Router 对象。

public class ConditionRouterFactory implements RouterFactory {

public static final String NAME = "condition";

@Override

public Router getRouter(URL url) {

return new ConditionRouter(url);

}

}

然后看下ConditionRouter的构造函数

public ConditionRouter(URL url) {

this.url = url;

// 获取 priority、force和enabled配置

this.priority = url.getParameter(PRIORITY_KEY, 0);

this.force = url.getParameter(FORCE_KEY, false);

this.enabled = url.getParameter(ENABLED_KEY, true);

// 获取路由规则

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<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(String rule)方法中,源码如下(来自官网):

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);

}

// 如果分隔符为 &,表明接下来也是一个条件

elseif ("&".equals(separator)) {

// 尝试从 condition 获取 MatchPair

if (condition.get(content) == null) {

// 未获取到 MatchPair,重新创建一个,并放入 condition 中

pair = new MatchPair();

condition.put(content, pair);

} else {

pair = condition.get(content);

}

}

// 分隔符为 =

elseif ("=".equals(separator)) {

if (pair == null)

throw new ParseException("Illegal route rule ...");

values = pair.matches;

// 将 content 存入到 MatchPair 的 matches 集合中

values.add(content);

}

// 分隔符为 !=

elseif ("!=".equals(separator)) {

if (pair == null)

throw new ParseException("Illegal route rule ...");

values = pair.mismatches;

// 将 content 存入到 MatchPair 的 mismatches 集合中

values.add(content);

}

// 分隔符为 ,

elseif (",".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;

}

通过源码可以总结得出parseRule由正则表达式和一个 while 循环以及数个条件分支组成。

服务路由的源码

服务路由的入口方法是 ConditionRouter 的 router 方法,该方法定义在 Router 接口中。

@Override

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)

throws RpcException {

if (!enabled) {

return 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;

}

// 如果匹配成功,再对服务提供者进行匹配

for (Invoker<T> invoker : invokers) {

if (matchThen(invoker.getUrl(), url)) {

result.add(invoker);

}

}

if (!result.isEmpty()) {

return result;

} elseif (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 whenCondition == null || whenCondition.isEmpty()

|| matchCondition(whenCondition, url, null, invocation); // 进行条件匹配

}

private boolean matchThen(URL url, URL param) {

// 服务提供者条件为 null 或空,表示禁用服务

return !(thenCondition == null || thenCondition.isEmpty())

&& matchCondition(thenCondition, url, param, null); // 进行条件匹配

}

再配合之前Directory的代码分析可以得出

  • 监听注册中心节点的变化,如果有节点新增或者删除,就会触发 notify 方法来更新;
  • 当获取了当前服务的 Invoker 列表后,如果配置了路由规则,则使用该路由规则过滤 Invoker 列表。

以上是 Dubbo进阶(十四):Router的实现 的全部内容, 来源链接: utcz.com/a/25356.html

回到顶部