前言
首先,服务路由是干什么的?
刷新invoker的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者。
服务路由包含一条路由规则,规定了服务消费者可调用哪些服务提供者。
Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。文中主要分析最常用的条件路由 ConditionRouter.
推荐阅读:
https://dubbo.incubator.apache.org/zh-cn/
https://segmentfault.com/blog/dubboanalysis
路由规则
条件路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。比如有这样一条规则:
| 1
 | host = 10.20.153.10 => host = 10.20.153.11
 | 
该条规则表示 IP 为 10.20.153.10 的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下:
| 1
 | [服务消费者匹配条件] => [服务提供者匹配条件]
 | 
如果服务消费者匹配条件为空,表示不对服务消费者进行限制。如果服务提供者匹配条件为空,表示对某些服务消费者禁用服务。
条件路由实现类 ConditionRouter 在进行工作前,需要先对用户配置的路由规则进行解析,得到一系列的条件。然后再根据这些条件对服务进行路由。本章将分两节进行说明,节介绍表达式解析过程。节介绍服务路由的过程。下面,我们先从表达式解析过程看起。
表达式解析
条件表达式的解析过程始于 ConditionRouter 的构造方法.该构造方法会在RegistryDirectory#notify中被调用,所以参数是从zk的routers节点下的url,下面开始分析:
| 12
 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
 
 | 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);
 
 this.whenCondition = when;
 this.thenCondition = then;推荐阅读:
 
 https:
 
 https:
 } catch (ParseException e) {
 throw new IllegalStateException(e.getMessage(), e);
 }
 }
 
 | 
假如我们这里的路由规则为
| 1
 | host = 10.20.153.10 => host = 10.20.153.11
 | 
首先会被分割为:
| 12
 
 | String whenRule = rule.substring(0, i).trim(); String thenRule = rule.substring(i + 2).trim();
 
 | 
通过parseRule()解析.结果的Map是Map<String, MatchPair>类型.而MatchPair是一个内部类,它的定义如下
| 12
 3
 4
 
 | private static final class MatchPair {final Set<String> matches = new HashSet<String>();
 final Set<String> mismatches = new HashSet<String>();
 }
 
 | 
parseRule()的字符串解析代码这里就不分析了.最后我们得到的结果:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | ----- whenCondition ------{
 "host": {
 "matches": ["10.20.153.10"],
 "mismatches": []
 }
 }
 ----- thenCondition ------
 {
 "host": {
 "matches": ["10.20.153.11"],
 "mismatches": []
 }
 }
 
 | 
最后把whenCondition和thenCondition进行保存.
再看一个parseRule()的例子.如果参数String为
| 12
 
 | > host = 2.2.2.2 & host != 1.1.1.1 & method = hello>
 
 | 
那么parseRule()返回的结果是
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | > {>  "host": {
 >      "matches": ["2.2.2.2"],
 >      "mismatches": ["1.1.1.1"]
 >  },
 >  "method": {
 >      "matches": ["hello"],
 >      "mismatches": []
 >  }
 > }
 >
 
 | 
ConditionRouterTest类里面有许多匹配的测试示例,可以参考.
服务路由
服务路由就是调用ConditionRouter的route()方法.这个方法在什么时候被调用呢.典型的例子就是在RegistryDirectory#toMethodInvokers,所有Invoker创建完成的时候进行调用.也就是说服务路由的过程就是通过之前加载的路由条件筛选出合适的invoker的过程.
匹配的细节就不看了.这里简单看route()的源代码:
| 12
 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
 
 | @Overridepublic <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
 throws RpcException {
 
 if (invokers == null || invokers.isEmpty()) {
 return invokers;
 }
 try {
 
 
 
 
 
 
 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;
 } 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;
 }
 
 | 
最后
在上一章为止一直在用官方的example demo来进行演示.而demo运行时AbstractDirectory的router变量是null.也就是router没有被生成,没有进行服务路由的环节.因此本章就没有进行太多的代码跟踪.
简单说一下router的创建时机,其实这部分应该放在文章的开头去讲的,不知为何我尝试创建路由规则的时候失败了(待会会讲),所以下面的内容只是根据源码的猜测,可能不准确:
AbstractDirectory的router变量的构造时机,也就是ConditionRouter的构造时机,一共有两次.一次是在AbstractDirectory的构造函数的参数中.还有一次是在RegistryDirectory的notify()里面.
而RegistryDirectory的notify()方法会从zk中获取router的URL.demo中获取到的URL是无效的(不存在),所以就没有构造成功.如果这里获取到有效的url,ConditionRouter就会被构造,并启动服务路由.
那么该如何创建路由呢?
官方文档说需要通过Dubbo-Admin写入路由规则.不知为何我在创建路由规则的页面保存不了..如果这里创建成功了,RegistryDirectory的notify()方法应该就会获取到有效的router的URL,并创建ConditionRouter,启动服务路由.
路由规则和匹配的逻辑文中都没有详细介绍,感觉以后要用的时候再查就行了.其实关于服务路由知道这一句话就够了:
服务路由的过程就是通过之前加载的路由条件筛选出合适的invoker的过程