资讯专栏INFORMATION COLUMN

dubbo源码解析(四十)集群——router

FullStackDeveloper / 1210人阅读

摘要:源码分析一创建一个该类是基于条件表达式规则路由工厂类。路由工厂获得配置项,默认为获得获得类型读取规则获得脚本路由获得路由后记该部分相关的源码解析地址该文章讲解了集群中关于路由规则实现的部分。

集群——router
目标:介绍dubbo中集群的路由,介绍dubbo-cluster下router包的源码。
前言

路由规则 决定一次 dubbo 服务调用的目标服务器,分为条件路由规则和脚本路由规则,并且支持可扩展 。

源码分析 (一)ConditionRouterFactory
public class ConditionRouterFactory implements RouterFactory {

    public static final String NAME = "condition";

    @Override
    public Router getRouter(URL url) {
        // 创建一个ConditionRouter
        return new ConditionRouter(url);
    }

}

该类是基于条件表达式规则路由工厂类。

(二)ConditionRouter

该类是基于条件表达式的路由实现类。关于给予条件表达式的路由规则,可以查看官方文档:

官方文档地址:http://dubbo.apache.org/zh-cn...
1.属性
private static final Logger logger = LoggerFactory.getLogger(ConditionRouter.class);
/**
 * 分组正则匹配
 */
private static Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)s*([^&!=,s]+)");
/**
 * 路由规则 URL
 */
private final URL url;
/**
 * 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
 */
private final int priority;
/**
 * 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false 。
 */
private final boolean force;
/**
 * 消费者匹配条件集合,通过解析【条件表达式 rule 的 `=>` 之前半部分】
 */
private final Map whenCondition;
/**
 * 提供者地址列表的过滤条件,通过解析【条件表达式 rule 的 `=>` 之后半部分】
 */
private final Map thenCondition;
2.构造方法
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 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);
    }
}
3.MatchPair
private static final class MatchPair {
    /**
     * 匹配的值的集合
     */
    final Set matches = new HashSet();
    /**
     * 不匹配的值的集合
     */
    final Set mismatches = new HashSet();

    /**
     * 判断value是否匹配matches或者mismatches
     * @param value
     * @param param
     * @return
     */
    private boolean isMatch(String value, URL param) {
        // 只匹配 matches
        if (!matches.isEmpty() && mismatches.isEmpty()) {
            for (String match : matches) {
                if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                    // 匹配上了返回true
                    return true;
                }
            }
            // 没匹配上则为false
            return false;
        }
        // 只匹配 mismatches
        if (!mismatches.isEmpty() && matches.isEmpty()) {
            for (String mismatch : mismatches) {
                if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                    // 如果匹配上了,则返回false
                    return false;
                }
            }
            // 没匹配上,则为true
            return true;
        }
        // 匹配 matches和mismatches
        if (!matches.isEmpty() && !mismatches.isEmpty()) {
            //when both mismatches and matches contain the same value, then using mismatches first
            for (String mismatch : mismatches) {
                if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                    // 匹配上则为false
                    return false;
                }
            }
            for (String match : matches) {
                if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                    // 匹配上则为true
                    return true;
                }
            }
            return false;
        }
        return false;
    }
}

该类是内部类,封装了匹配的值,每个属性条件。并且提供了判断是否匹配的方法。

4.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;
    // 正则表达式匹配
    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 (separator == null || separator.length() == 0) {
            pair = new MatchPair();
            // 保存条件
            condition.put(content, pair);
        }
        // The KV part of the condition expression
        else if ("&".equals(separator)) {
            // 把参数的条件表达式放入condition
            if (condition.get(content) == null) {
                pair = new MatchPair();
                condition.put(content, pair);
            } else {
                pair = condition.get(content);
            }
        }
        // The Value in the KV part.
        // 把值放入values
        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.
        // 把不等于的条件限制也放入values
        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.
        // 如果以.分隔的也放入values
        else if (",".equals(separator)) { // Should be seperateed 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());
            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;
}

该方法是根据规则解析路由配置内容。具体的可以参照官网的配置规则来解读这里每一个分割取值作为条件的过程。

5.route
@Override
public  List> route(List> invokers, URL url, Invocation invocation)
        throws RpcException {
    // 为空,直接返回空 Invoker 集合
    if (invokers == null || invokers.isEmpty()) {
        return invokers;
    }
    try {
        // 如果不匹配 `whenCondition` ,直接返回 `invokers` 集合,因为不需要走 `whenThen` 的匹配
        if (!matchWhen(url, invocation)) {
            return invokers;
        }
        List> result = new ArrayList>();
        // 如果thenCondition为空,则直接返回空
        if (thenCondition == null) {
            logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
            return result;
        }
        // 遍历invokers
        for (Invoker invoker : invokers) {
            // 如果thenCondition匹配,则加入result
            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;
}

该方法是进行路由规则的匹配,分别对消费者和提供者进行匹配。

6.matchCondition
private boolean matchCondition(Map condition, URL url, URL param, Invocation invocation) {
    Map sample = url.toMap();
    // 是否匹配
    boolean result = false;
    // 遍历条件
    for (Map.Entry matchPair : condition.entrySet()) {
        String key = matchPair.getKey();
        String sampleValue;
        //get real invoked method name from invocation
        // 获得方法名
        if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) {
            sampleValue = invocation.getMethodName();
        } else {
            //
            sampleValue = sample.get(key);
            if (sampleValue == null) {
                sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key);
            }
        }
        if (sampleValue != null) {
            // 如果不匹配条件值,返回false
            if (!matchPair.getValue().isMatch(sampleValue, param)) {
                return false;
            } else {
                // 匹配则返回true
                result = true;
            }
        } else {
            //not pass the condition
            // 如果匹配的集合不为空
            if (!matchPair.getValue().matches.isEmpty()) {
                // 返回false
                return false;
            } else {
                // 返回true
                result = true;
            }
        }
    }
    return result;
}

该方法是匹配条件的主要逻辑。

(三)ScriptRouterFactory

该类是基于脚本的路由规则工厂类。

public class ScriptRouterFactory implements RouterFactory {

    public static final String NAME = "script";

    @Override
    public Router getRouter(URL url) {
        // 创建ScriptRouter
        return new ScriptRouter(url);
    }

}
(四)ScriptRouter

该类是基于脚本的路由实现类

1.属性
private static final Logger logger = LoggerFactory.getLogger(ScriptRouter.class);

/**
 * 脚本类型 与 ScriptEngine 的映射缓存
 */
private static final Map engines = new ConcurrentHashMap();

/**
 * 脚本
 */
private final ScriptEngine engine;

/**
 * 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0 。
 */
private final int priority;

/**
 * 路由规则
 */
private final String rule;

/**
 * 路由规则 URL
 */
private final URL url;
2.route
@Override
@SuppressWarnings("unchecked")
public  List> route(List> invokers, URL url, Invocation invocation) throws RpcException {
    try {
        List> invokersCopy = new ArrayList>(invokers);
        Compilable compilable = (Compilable) engine;
        // 创建脚本
        Bindings bindings = engine.createBindings();
        // 设置invokers、invocation、context
        bindings.put("invokers", invokersCopy);
        bindings.put("invocation", invocation);
        bindings.put("context", RpcContext.getContext());
        // 编译脚本
        CompiledScript function = compilable.compile(rule);
        // 执行脚本
        Object obj = function.eval(bindings);
        // 根据结果类型,转换成 (List> 类型返回
        if (obj instanceof Invoker[]) {
            invokersCopy = Arrays.asList((Invoker[]) obj);
        } else if (obj instanceof Object[]) {
            invokersCopy = new ArrayList>();
            for (Object inv : (Object[]) obj) {
                invokersCopy.add((Invoker) inv);
            }
        } else {
            invokersCopy = (List>) obj;
        }
        return invokersCopy;
    } catch (ScriptException e) {
        //fail then ignore rule .invokers.
        // 发生异常,忽略路由规则,返回全 `invokers` 集合
        logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
        return invokers;
    }
}

该方法是根据路由规则选择invoker的实现逻辑。

(五)FileRouterFactory

该类是装饰者,对RouterFactory进行了功能增强,增加了从文件中读取规则。

public class FileRouterFactory implements RouterFactory {

    public static final String NAME = "file";

    /**
     * 路由工厂
     */
    private RouterFactory routerFactory;

    public void setRouterFactory(RouterFactory routerFactory) {
        this.routerFactory = routerFactory;
    }

    @Override
    public Router getRouter(URL url) {
        try {
            // Transform File URL into Script Route URL, and Load
            // file:///d:/path/to/route.js?router=script ==> script:///d:/path/to/route.js?type=js&rule=
            // 获得 router 配置项,默认为 script
            String protocol = url.getParameter(Constants.ROUTER_KEY, ScriptRouterFactory.NAME); // Replace original protocol (maybe "file") with "script"
            String type = null; // Use file suffix to config script type, e.g., js, groovy ...
            // 获得path
            String path = url.getPath();
            // 获得类型
            if (path != null) {
                int i = path.lastIndexOf(".");
                if (i > 0) {
                    type = path.substring(i + 1);
                }
            }
            // 读取规则
            String rule = IOUtils.read(new FileReader(new File(url.getAbsolutePath())));

            boolean runtime = url.getParameter(Constants.RUNTIME_KEY, false);
            // 获得脚本路由url
            URL script = url.setProtocol(protocol).addParameter(Constants.TYPE_KEY, type).addParameter(Constants.RUNTIME_KEY, runtime).addParameterAndEncoded(Constants.RULE_KEY, rule);

            // 获得路由
            return routerFactory.getRouter(script);
        } catch (IOException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

}
后记
该部分相关的源码解析地址:https://github.com/CrazyHZM/i...

该文章讲解了集群中关于路由规则实现的部分。接下来我将开始对集群模块关于Mock部分进行讲解。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73255.html

相关文章

  • dubbo源码解析四十五)服务引用过程

    摘要:服务引用过程目标从源码的角度分析服务引用过程。并保留服务提供者的部分配置,比如版本,,时间戳等最后将合并后的配置设置为查询字符串中。的可以参考源码解析二十三远程调用的一的源码分析。 dubbo服务引用过程 目标:从源码的角度分析服务引用过程。 前言 前面服务暴露过程的文章讲解到,服务引用有两种方式,一种就是直连,也就是直接指定服务的地址来进行引用,这种方式更多的时候被用来做服务测试,不...

    xiaowugui666 评论0 收藏0
  • dubbo源码解析四十三)2.7新特性

    摘要:大揭秘目标了解的新特性,以及版本升级的引导。四元数据改造我们知道以前的版本只有注册中心,注册中心的有数十个的键值对,包含了一个服务所有的元数据。 DUBBO——2.7大揭秘 目标:了解2.7的新特性,以及版本升级的引导。 前言 我们知道Dubbo在2011年开源,停止更新了一段时间。在2017 年 9 月 7 日,Dubbo 悄悄的在 GitHub 发布了 2.5.4 版本。随后,版本...

    qqlcbb 评论0 收藏0
  • dubbo源码解析四十六)消费端发送请求过程

    摘要:可以参考源码解析二十四远程调用协议的八。十六的该类也是用了适配器模式,该类主要的作用就是增加了心跳功能,可以参考源码解析十远程通信层的四。二十的可以参考源码解析十七远程通信的一。 2.7大揭秘——消费端发送请求过程 目标:从源码的角度分析一个服务方法调用经历怎么样的磨难以后到达服务端。 前言 前一篇文章讲到的是引用服务的过程,引用服务无非就是创建出一个代理。供消费者调用服务的相关方法。...

    fish 评论0 收藏0
  • dubbo源码解析四十一)集群——Mock

    摘要:源码分析一创建该类是服务降级的装饰器类,对进行了功能增强,增强了服务降级的功能。注意隐式契约尽管描述被添加到接口声明中,但是可扩展性是一个问题。获得服务类型获得创建加入集合该方法是获得。 集群——Mock 目标:介绍dubbo中集群的Mock,介绍dubbo-cluster下关于服务降级和本地伪装的源码。 前言 本文讲解两块内容,分别是本地伪装和服务降级,本地伪装通常用于服务降级,比如...

    ivydom 评论0 收藏0
  • dubbo源码解析四十八)异步化改造

    摘要:大揭秘异步化改造目标从源码的角度分析的新特性中对于异步化的改造原理。看源码解析四十六消费端发送请求过程讲到的十四的,在以前的逻辑会直接在方法中根据配置区分同步异步单向调用。改为关于可以参考源码解析十远程通信层的六。 2.7大揭秘——异步化改造 目标:从源码的角度分析2.7的新特性中对于异步化的改造原理。 前言 dubbo中提供了很多类型的协议,关于协议的系列可以查看下面的文章: du...

    lijinke666 评论0 收藏0

发表评论

0条评论

FullStackDeveloper

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<