Dubbo 源码分析 – 集群容错之 Router

1. 简介

上一篇文章分析了集群容错的第一部分 – 服务目录 Directory。服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由。上一篇文章关于服务路由相关逻辑没有细致分析,一笔带过了,本篇文章将对此进行详细的分析。首先,先来介绍一下服务目录是什么。服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。其中条件路由是我们最常使用的,标签路由暂未在我所分析的 2.6.4 版本中提供,该实现会在 2.7.0 版本中提供。本篇文章将分析条件路由相关源码,脚本路由和标签路由这里就不分析了。下面进入正题。

2. 源码分析

条件路由规则有两个条件组成,分别用于对服务消费者和提供者进行匹配。比如有这样一条规则:

host = 10.20.153.10 => host = 10.20.153.11

该条规则表示 IP 为 10.20.153.10 的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下:

[服务消费者匹配条件] => [服务提供者匹配条件]

如果服务消费者匹配条件为空,表示不对服务消费者进行限制。如果服务提供者匹配条件为空,表示对某些服务消费者禁用服务。Dubbo 官方文档对条件路由进行了比较详细的介绍,大家可以参考下,这里就不过多说明了。

条件路由实现类 ConditionRouter 需要对用户配置的路由规则进行解析,得到一系列的条件。然后再根据这些条件对服务进行路由。本章将分两节进行说明,2.1节介绍表达式解析过程。2.2 节介绍服务路由的过程。接下来,我们先从表达式解析过程看起。

2.1 表达式解析

条件路由规则是一条字符串,对于 Dubbo 来说,它并不能直接理解字符串的意思,需要将其解析成内部格式才行。条件表达式的解析过程始于 ConditionRouter 的构造方法,下面一起看一下:

public ConditionRouter(URL url) {
    this.url = url;
    // 获取 priority 和 force 配置
    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;
    } catch (ParseException e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
}

如上,ConditionRouter 构造方法先是对路由规则做预处理,然后调用 parseRule 方法分别对服务提供者和消费者规则进行解析,最后将解析结果赋值给 whenCondition 和 thenCondition 成员变量。ConditionRouter 构造方法不是很复杂,这里就不多说了。下面我们把重点放在 parseRule 方法上,在详细介绍这个方法之前,我们先来看一个内部类。

private static final class MatchPair {
    final Set<String> matches = new HashSet<String>();
    final Set<String> mismatches = new HashSet<String>();
}

MatchPair 内部包含了两个 Set 型的成员变量,分别用于存放匹配和不匹配的条件。这个类两个成员变量会在 parseRule 方法中被用到,下面来看一下。

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

以上就是路由规则的解析逻辑,该逻辑由正则表达式 + 一个 while 循环 + 数个条件分支组成。下面使用一个示例对解析逻辑进行演绎。示例为 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

现在线程进入 while 循环:

第一次循环:分隔符 separator = null,content = “host”。此时创建 MatchPair 对象,并存入到 condition 中,condition = {“host”: MatchPair@123}

第二次循环:分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@123。此时将 2.2.2.2 放入到 MatchPair@123 对象的 matches 集合中。

第三次循环:分隔符 separator = “&”,content = “host”。host 已存在于 condition 中,因此 pair = MatchPair@123。

第四次循环:分隔符 separator = “!=”,content = “1.1.1.1”,pair = MatchPair@123。此时将 1.1.1.1 放入到 MatchPair@123 对象的 mismatches 集合中。

第五次循环:分隔符 separator = “&”,content = “method”。condition.get(“method”) = null,因此新建一个 MatchPair 对象,并放入到 condition 中。此时 condition = {“host”: MatchPair@123, “method”: MatchPair@ 456}

第六次循环:分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@456。此时将 hello 放入到 MatchPair@456 对象的 matches 集合中。

循环结束,此时 condition 的内容如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值