Dubbo routing mechanism is to call a specific service according to the routing rules set by the service provider during the invocation between services.

Routing service Structure

Dubbo implements routing by implementing the RouterFactory interface. Current version dubo-2.7.5 implements the following interface classes:

The routing implementation factory class is under the Router package

Because RouterFactory is an SPI interface and has @Adaptive(“protocol”) annotation on the RouterFactory#getRouter method to obtain the route, the required factory class is dynamically invoked when obtaining the route.

As you can see, the getRouter method returns a Router interface with the following information

Router# Route is the entry to the service route, and there are specific Router implementation classes for different types of route factories.

Router# Router is called to filter invokers that match the current routing rules.

Service Routing Implementation

ConditionRouter ConditionRouter is the most common implementation of ConditionRouter. ConditionRouter is the most common implementation of ConditionRouter. ConditionRouter is the most common implementation of ConditionRouter, and ConditionRouter is the most common.

Conditional routing parameter rules

Before analyzing conditional routing, learn about the parameter Settings of conditional routing. The official documents are as follows:

Conditional routing rules are as follows:

Conditional routing implementation analysis

Analysis of route implementation, mainly analysis of factory class xxxRouterFactory#getRouter and xxxRouter#route methods.

ConditionRouterFactory#getRouter

ConditionRouterFactory creates ConditionRouter to initialize the configuration.

In ConditionRouter constructor, fetch the rule in string form from the URL and parse the rule in ConditionRouter#init.

public void init(String rule) {
    try {
        if (rule == null || rule.trim().length() == 0) {
            throw new IllegalArgumentException("Illegal route rule!");
        }
        // Remove the consumer. And provider. Identifiers
        rule = rule.replace("consumer."."").replace("provider."."");
        // Gets the separator for the consumer match criteria and the provider address match criteria
        int i = rule.indexOf("= >");
        // The consumer matches the condition
        String whenRule = i < 0 ? null : rule.substring(0, i).trim();
        // Provider address matching condition
        String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
        // Parse consumer routing rules
        Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
        // Parse the provider routing rule
        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 newIllegalStateException(e.getMessage(), e); }}Copy the code

Split the consumer matching condition and provider matching condition with the => delimiter in the string of routing rules. After parsing the two routing rules, assign the value to the variable of the current object.

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

// Regress to verify routing rules
protected static final Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");


private static Map<String, MatchPair> parseRule(String rule)
        throws ParseException {
    For example, host => 127.0.0.1 holds the mapping between host and 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()) { 
        // Gets the content of the pre-re partial match (the first parenthesis)
        String separator = matcher.group(1);
        // Gets the content of the partial match (second parenthesis) after the re
        String content = matcher.group(2);
        // If the front part of the fetch is empty, it indicates the starting position of the rule, and the current content must be a conditional variable
        if (StringUtils.isEmpty(separator)) {
            pair = new MatchPair();
            condition.put(content, pair);
        }
        // If the delimiter is &, content is the conditional variable
        else if ("&".equals(separator)) {
            // The current content is the conditional variable used as the mapping set key, if not, add an element
            if (condition.get(content) == null) {
                pair = new MatchPair();
                condition.put(content, pair);
            } else{ pair = condition.get(content); }}// If the current separator is =, the current content is the value of the conditional variable
        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());
            }
            // Since pair has not been reinitialized, it is still the object of the previous condition variable, so the current condition variable value can be assigned to the reference object
            values = pair.matches;
            values.add(content);
        }
        // If the current separator is =, the current content is also the conditional variable value
        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());
            }
            // The same as when =
            values = pair.mismatches;
            values.add(content);
        }
        // If the current separator is ',', then the current content is also the conditional variable value
        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());
            }
            // Add data directly to the conditional variable value collection
            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;
}
Copy the code

Matches = mismatches, matches = matches, matches = mismatches, matches = matches, matches = mismatches, matches The value of the condition variable of = is placed in mismatches for the unmatched routing rule. In the assignment process, the code is still pretty elegant.

Matches and mismatches hold the value of a condition variable.

ConditionRouter#route

Router# Route matches a set of invokers that match routing rules.

// The variable to be copied during initialization
// Consumer condition matching rule
protected Map<String, MatchPair> whenCondition;
// Provider condition matching rule
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;
    }
    // Verify that invokers is empty
    if (CollectionUtils.isEmpty(invokers)) {
        return invokers;
    }
    try {
        // Verifies that the consumer has a rule match, and returns the Invoker passed in if not
        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;
        }
        // Iterate over the incoming Invokers to see if the matching provider has a rule match
        for (Invoker<T> invoker : invokers) {
            if(matchThen(invoker.getUrl(), url)) { result.add(invoker); }}Return the Invoker list of result if result is not empty, or if the current object 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));
            returnresult; }}catch (Throwable t) {
        logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
    }
    return invokers;
}

Copy the code

As you can see in the code above, the Invoker parameter passed in will not be returned as long as the consumer does not have a matching rule or the provider does not have a matching rule and force=false.

The methods for matching consumer 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 a Map and match it with the whenCondition or thenCondition.

During the matching process, if the key (sampleValue value) has a corresponding value, the match is performed using the MatchPair#isMatch method.

private boolean isMatch(String value, URL param) {
    // There are matched rules. There are no unmatched rules
    if(! matches.isEmpty() && mismatches.isEmpty()) {// If the list of unmatched rules is empty, return true as long as the matched rules are matched
        for (String match : matches) {
            if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                return true; }}return false;
    }
    // There is no rule that can be matched
    if(! mismatches.isEmpty() && matches.isEmpty()) {// If the list of unmatched rules exists, false is returned
        for (String mismatch : mismatches) {
            if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                return false; }}return true;
    }
    // There are matched rules and there are unmatched rules
    if(! matches.isEmpty() && ! mismatches.isEmpty()) {// If the list of unmatched rules exists, then false is returned
        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;
    }
    // The last thing left is when both the matchable and unmatchable rules are empty
    return false;
}
Copy the code

The matching procedure then calls the UrlUtils#isMatchGlobPattern implementation

public static boolean isMatchGlobPattern(String pattern, String value, URL param) {
    // If it starts with $, get the corresponding value in the 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;
    }
    // Get the wildcard position
    int i = pattern.lastIndexOf(The '*');
    // If there is no "*" wildcard in value, the entire string value matches
    if (i == -1) {
        return value.equals(pattern);
    }
    // If "*" comes last, match the string before the string "*"
    else if (i == pattern.length() - 1) {
        return value.startsWith(pattern.substring(0, i));
    }
    // If "*" comes first, match the string after the string "*"
    else if (i == 0) {
        return value.endsWith(pattern.substring(i + 1));
    }
    // If "*" is not at both ends of the string, then both sides of the string "*" are matched
    else {
        String prefix = pattern.substring(0, i);
        String suffix = pattern.substring(i + 1);
        returnvalue.startsWith(prefix) && value.endsWith(suffix); }}Copy the code

In this way, all conditional routing rule matching is completed. Although the code seems to be complicated, it is better to clarify the rules and ideas and analyze them step by step. The premise is to be familiar with the usage and form of relevant parameters, otherwise the code is difficult to understand.

The last

Purely from the logic, if we can master the implementation of conditional routing, to study the implementation of other routes, I believe there will be no big problem. For example, you need to be able to use a script execution engine, or you won’t understand its code. Finally, dubbo-admin can set the routing mechanism. You can try various rules to better 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