Implementation of Dubbo routing mechanism

Dubbo routing mechanism is to decide which specific service to call according to the set routing rules when calling between services.

Routing service structure

Dubbo implements routing by implementing RouterFactory interface. The current version of dubbo-2.7.5 implements this interface class as follows:

The routing implementation factory class is under the router package

Since RouterFactory is an SPI interface, and at the same time there is an @Adaptive ("protocol") annotation on the GetRouterFactory # getRouter method, so the required factory class will be dynamically called when getting the route.

You can see that the getRouter method returns a Router interface. The interface information is as follows

Router # route is the entry point of service routing. For different types of routing factories, there is a specific Router implementation class.

The above is to obtain the specific Router by parsing the URL, and filter out the invokers that meet the current routing rules by calling Router # router.

Service routing implementation

The above shows the routing implementation class. Among these several implementation types, ConditionRouter conditional routing is the most commonly used type. Due to the limited space of this article, this article will not analyze all the routing types one by one, only analyze the conditional routing specifically, as long as you understand this One type, other types of analysis can be easily mastered.

Conditional routing parameter rules

Before analyzing conditional routing, first understand the parameter configuration of conditional routing, the official document is as follows:

The content of conditional routing rules is as follows:

Conditional routing analysis

Analyze the routing implementation, mainly analyze the xxxRouterFactory # getRouter and xxxRouter # route methods of the factory class.

ConditionRouterFactory # getRouter

ConditionRouterFactory initializes the configuration of related parameters by creating ConditionRouter objects.

In the ConditionRouter constructor, the rules of the string format of the rule are obtained from the URL, and the parsing rules are in the ConditionRouter # init initialization method.

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);
    }
}
复制代码

Using the =>separator in the routing rule string as the separator, the consumer matching condition and the provider matching condition are divided, after parsing the two routing rules, they are assigned to the variables of the current object.

Call the parseRule method to parse the consumer and server routing rules.

// 正则验证路由规则
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;
}
复制代码

The above is the process of parsing conditional routing rules. The value of the condition variable is stored in the matches and mismatches attributes in MatchPair. The value of the condition variable is placed in matches that can be matched , =and ,the value !=of the condition variable is placed in mismatches that cannot match the routing rule. in. During the assignment process, the code is relatively elegant.

In fact, matches and mismatches store condition variable values.

ConditionRouter#route

Router#routeThe role is to match the Invoker set that meets the routing rules.

// 在初始化中进行被复制的变量
// 消费者条件匹配规则
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;
}

复制代码

As you can see in the above code, as long as the consumer has no matching rules or the provider has no matching rules and force = false, the Invoker of the incoming parameters will not be returned.

The methods of matching consumer routing rules and provider routing rules are matchWhen and matchThen

Both matching methods are implemented by calling the same method matchCondition. Convert the consumer or provider URL to Map, and then match with whenCondition or thenCondition.

During the matching process, if there is a corresponding value for the key (that is, the sampleValue value), the match is performed again by the MatchPair # isMatch method.

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;
}
复制代码

The matching process is then called 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);
    }
}
复制代码

In this way, all the conditional routing rules are matched. Although the code seems to be more complicated, it is still better to clarify the rules and ideas step by step. The premise is to be familiar with the usage and form of related parameters, otherwise the code is more difficult to understand.

At last

Simply logically, if you can master the implementation of conditional routing and study other ways of routing implementation, I believe there will not be too many problems. Just for example, the implementation of script routing, you have to use the script execution engine as the premise, otherwise you will not understand its code. Finally, routing can be set on dubbo-admin. You can try various usage rules, and you can better understand and understand the implementation of the routing mechanism through practical operation.


Personal blog: ytao.top

Pay attention to the public number [ytao], more original good articles

My public account

Guess you like

Origin juejin.im/post/5e94709cf265da47a83134b9