dubbo 服务路由

目录
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将被解析为


dubbo 服务路由_第1张图片
image.png

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

你可能感兴趣的:(dubbo 服务路由)