ダボルーティングメカニズムの実装

ダボルーティングメカニズムは、サービス間で呼び出すときに、設定されたルーティングルールに従って、呼び出す特定のサービスを決定することです。

ルーティングサービスの構造

Dubboは、RouterFactoryインターフェイスを実装することによってルーティングを実装します。dubbo-2.7.5の現在のバージョンは、このインターフェースクラスを次のように実装します。

ルーティング実装ファクトリクラスはルーターパッケージの下にあります

RouterFactoryはSPIインターフェースであり、同時にGetRouterFactory#getRouterメソッドに@Adaptive( "protocol")アノテーションがあるため、ルートを取得するときに必要なファクトリクラスが動的に呼び出されます。

getRouterメソッドがルーターインターフェースを返すことがわかります。インターフェース情報は次のとおりです。

ルーター#ルートは、サービスルーティングのエントリポイントです。ルーティングファクトリのタイプごとに、特定のルーター実装クラスがあります。

上記は、URLを解析して特定のルーターを取得し、Router#routerを呼び出して現在のルーティングルールを満たす呼び出し元を除外することです。

サービスルーティングの実装

上記はルーティング実装クラスを示しています。これらのいくつかの実装タイプの中で、ConditionRouter条件付きルーティングは最も一般的に使用されるタイプです。この記事ではスペースが限られているため、この記事ではすべてのルーティングタイプを1つずつ分析するのではなく、条件付きルーティングのみを分析します。 1つのタイプ、他のタイプの分析を簡単に習得できます。

条件付きルーティングパラメータルール

条件付きルーティングを分析する前に、まず条件付きルーティングのパラメーター構成を理解してください。公式文書は次のとおりです。

条件付きルーティングルールの内容は次のとおりです。

条件付きルーティング分析

ルーティングの実装を分析し、主にファクトリクラスのxxxRouterFactory#getRouterおよびxxxRouter#ルートメソッドを分析します。

ConditionRouterFactory#getRouter

ConditionRouterFactoryは、ConditionRouterオブジェクトを作成することにより、関連するパラメーターの構成を初期化します。

ConditionRouterコンストラクターでは、ルールの文字列形式のルールはURLから取得され、解析ルールはConditionRouter#init初期化メソッドにあります。

public void init(String rule) {
    try {
        if (rule == null || rule.trim().length() == 0) {
            throw new IllegalArgumentException("Illegal route rule!");
        }
        // 去掉 consumer. 和 provider. 的标识
        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);
    }
}
复制代码

ルーティングルール文字列=>のセパレーターをセパレーターとして使用して、コンシューマー一致条件とプロバイダー一致条件を分割し、2つのルーティングルールを解析した後、それらを現在のオブジェクトの変数に割り当てます。

parseRuleメソッドを呼び出して、コンシューマーとサーバーのルーティングルールを解析します。

// 正则验证路由规则
protected static final Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");


private static Map<String, MatchPair> parseRule(String rule)
        throws ParseException {
    /**
     * 条件变量和条件变量值的映射关系
     * 比如 host => 127.0.0.1 则保存着 host 和 127.0.0.1 的映射关系
    */
    Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
    if (StringUtils.isBlank(rule)) {
        return condition;
    }
    // Key-Value pair, stores both match and mismatch conditions
    MatchPair pair = null;
    // Multiple values
    Set<String> values = null;
    final Matcher matcher = ROUTE_PATTERN.matcher(rule);
    while (matcher.find()) { 
        // 获取正则前部分匹配(第一个括号)的内容
        String separator = matcher.group(1);
        // 获取正则后部分匹配(第二个括号)的内容
        String content = matcher.group(2);
        // 如果获取前部分为空,则表示规则开始位置,则当前 content 必为条件变量
        if (StringUtils.isEmpty(separator)) {
            pair = new MatchPair();
            condition.put(content, pair);
        }
        // 如果分隔符是 &,则 content 为条件变量
        else if ("&".equals(separator)) {
            // 当前 content 是条件变量,用来做映射集合的 key 的,如果没有则添加一个元素
            if (condition.get(content) == null) {
                pair = new MatchPair();
                condition.put(content, pair);
            } else {
                pair = condition.get(content);
            }
        }
        // 如果当前分割符是 = ,则当前 content 为条件变量值
        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());
            }
            // 由于 pair 还没有被重新初始化,所以还是上一个条件变量的对象,所以可以将当前条件变量值在引用对象上赋值
            values = pair.matches;
            values.add(content);
        }
        // 如果当前分割符是 = ,则当前 content 也是条件变量值
        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);
        }
        // 如果当前分割符为 ',',则当前 content 也为条件变量值
        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());
            }
            // 直接向条件变量值集合中添加数据
            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;
}
复制代码

値がルールをルーティング分析条件に一致する上記プロセスは、状態変数に格納されていることMatchPair、ミスマッチの属性である、=および,条件変数の値は、マッチングの一致に配置することができる!=ルーティングルールミスマッチにおける状態変数の値が一致しませんで。割り当てプロセス中、コードは比較的洗練されています。

実際、一致と不一致には条件変数の値が格納されます。

ConditionRouter#route

Router#route役割は、ルーティングルールを満たす呼び出し元セットを照合することです。

// 在初始化中进行被复制的变量
// 消费者条件匹配规则
protected Map<String, MatchPair> whenCondition;
// 提供者条件匹配规则
protected Map<String, MatchPair> thenCondition;


public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
        throws RpcException {
    if (!enabled) {
        return invokers;
    }
    // 验证 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;
        }
        // 遍历传入的 invokers,匹配提供者是否有规则匹配
        for (Invoker<T> invoker : invokers) {
            if (matchThen(invoker.getUrl(), url)) {
                result.add(invoker);
            }
        }
        // 如果 result 不为空,或当前对象 force=true 则返回 result 的 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(RULE_KEY));
            return result;
        }
    } catch (Throwable t) {
        logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
    }
    return invokers;
}

复制代码

上記のコードからわかるように、コンシューマーに一致ルールがないか、プロバイダーに一致ルールがなく、force = falseである限り、受信パラメーターのInvokerは返されません。

コンシューマールーティングルールとプロバイダールーティングルールを照合する方法は、matchWhenとmatchThenです。

両方のマッチングメソッドは、同じメソッドmatchConditionを呼び出すことによって実装されます。コンシューマーまたはプロバイダーのURLをMapに変換し、whenConditionまたはthenConditionと照合します。

マッチングプロセス中に、キーに対応する値(つまり、sampleValue値)がある場合、MatchPair#isMatchメソッドによってマッチングが再度実行されます。

private boolean isMatch(String value, URL param) {
    // 存在可匹配的规则,不存在不可匹配的规则
    if (!matches.isEmpty() && mismatches.isEmpty()) {
        // 不可匹配的规则列表为空时,只要可匹配的规则匹配上,直接返回 true
        for (String match : matches) {
            if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                return true;
            }
        }
        return false;
    }
    // 存在不可匹配的规则,不存在可匹配的规则
    if (!mismatches.isEmpty() && matches.isEmpty()) {
        // 不可匹配的规则列表中存在,则返回false
        for (String mismatch : mismatches) {
            if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                return false;
            }
        }
        return true;
    }
    // 存在可匹配的规则,也存在不可匹配的规则
    if (!matches.isEmpty() && !mismatches.isEmpty()) {
        // 都不为空时,不可匹配的规则列表中存在,则返回 false
        for (String mismatch : mismatches) {
            if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                return false;
            }
        }
        for (String match : matches) {
            if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                return true;
            }
        }
        return false;
    }
    // 最后剩下的是 可匹配规则和不可匹配规则都为空时
    return false;
}
复制代码

次に、一致するプロセスはUrlUtilsと呼ばれます#isMatchGlobPattern

public static boolean isMatchGlobPattern(String pattern, String value, URL param) {
    // 如果以 $ 开头,则获取 URL 中对应的值
    if (param != null && pattern.startsWith("$")) {
        pattern = param.getRawParameter(pattern.substring(1));
    }
    // 
    return isMatchGlobPattern(pattern, value);
}



public static boolean isMatchGlobPattern(String pattern, String value) {
    if ("*".equals(pattern)) {
        return true;
    }
    if (StringUtils.isEmpty(pattern) && StringUtils.isEmpty(value)) {
        return true;
    }
    if (StringUtils.isEmpty(pattern) || StringUtils.isEmpty(value)) {
        return false;
    }
    // 获取通配符位置
    int i = pattern.lastIndexOf('*');
    // 如果value中没有 "*" 通配符,则整个字符串值匹配
    if (i == -1) {
        return value.equals(pattern);
    }
    // 如果 "*" 在最后面,则匹配字符串 "*" 之前的字符串即可
    else if (i == pattern.length() - 1) {
        return value.startsWith(pattern.substring(0, i));
    }
    // 如果 "*" 在最前面,则匹配字符串 "*" 之后的字符串即可
    else if (i == 0) {
        return value.endsWith(pattern.substring(i + 1));
    }
    // 如果 "*" 不在字符串两端,则同时匹配字符串 "*" 左右两边的字符串
    else {
        String prefix = pattern.substring(0, i);
        String suffix = pattern.substring(i + 1);
        return value.startsWith(prefix) && value.endsWith(suffix);
    }
}
复制代码

このようにして、すべての条件付きルーティングルールが一致します。コードはより複雑に見えますが、ルールとアイデアを段階的に明確にすることをお勧めします。関連するパラメーターの使用法と形式に精通していることが前提です。そうでない場合、コードは理解しにくくなります。

最後

簡単に言えば、条件付きルーティングの実装をマスターし、他のルーティング実装方法を研究できれば、問題はそれほど多くないと思います。たとえば、スクリプトルーティングの実装では、前提としてスクリプト実行エンジンを使用する必要があります。そうしないと、そのコードを理解できません。最後に、dubbo-adminでルーティングを設定でき、さまざまな使用規則を試すことができ、実際の操作を通じてルーティングメカニズムの実装をよりよく理解し理解することができます。


個人ブログ: ytao.top

公開番号に注目[ytao]、より独創的な優れた記事

私の公開アカウント

おすすめ

転載: juejin.im/post/5e94709cf265da47a83134b9