Implementación del mecanismo de enrutamiento Dubbo

El mecanismo de enrutamiento de Dubbo es decidir a qué servicio específico llamar de acuerdo con las reglas de enrutamiento establecidas al llamar entre servicios.

Estructura de servicio de enrutamiento

Dubbo implementa el enrutamiento mediante la implementación de la interfaz RouterFactory. La versión actual de dubbo-2.7.5 implementa esta clase de interfaz de la siguiente manera:

La clase de fábrica de implementación de enrutamiento está bajo el paquete del enrutador

Dado que RouterFactory es una interfaz SPI, y al mismo tiempo hay una anotación @Adaptive ("protocolo") en el método GetRouterFactory # getRouter, por lo que la clase de fábrica requerida se llamará dinámicamente al obtener la ruta.

Puede ver que el método getRouter devuelve una interfaz de enrutador. La información de la interfaz es la siguiente

Router # route es el punto de entrada del enrutamiento de servicio. Para diferentes tipos de fábricas de enrutamiento, hay una clase de implementación de enrutador específica.

Lo anterior es para obtener el enrutador específico analizando la URL y filtrar los invocadores que cumplen con las reglas de enrutamiento actuales llamando al enrutador # enrutador.

Implementación de enrutamiento de servicios

Lo anterior muestra la clase de implementación de enrutamiento. Entre estos varios tipos de implementación, el enrutamiento condicional ConditionRouter es el tipo más utilizado. Debido al espacio limitado de este artículo, este artículo no analiza todos los tipos de enrutamiento uno por uno, sino que solo analiza el enrutamiento condicional específicamente. Un tipo, otros tipos de análisis se pueden dominar fácilmente.

Reglas de parámetros de enrutamiento condicional

Antes de analizar el enrutamiento condicional, primero comprenda la configuración de parámetros del enrutamiento condicional, el documento oficial es el siguiente:

El contenido de las reglas de enrutamiento condicional es el siguiente:

Análisis de enrutamiento condicional

Analice la implementación de enrutamiento, analice principalmente los métodos de ruta xxxRouterFactory # getRouter y xxxRouter # de la clase factory.

ConditionRouterFactory # getRouter

ConditionRouterFactory inicializa la configuración de parámetros relacionados creando objetos ConditionRouter.

En el constructor ConditionRouter, las reglas del formato de cadena de la regla se obtienen de la URL, y las reglas de análisis se encuentran en el método de inicialización 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);
    }
}
复制代码

Al usar el =>separador en la cadena de la regla de enrutamiento como el separador, la condición de coincidencia del consumidor y la condición de coincidencia del proveedor se dividen, después de analizar las dos reglas de enrutamiento, se asignan a las variables del objeto actual.

Llame al método parseRule para analizar las reglas de enrutamiento del consumidor y del servidor.

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

Lo anterior es el proceso de analizar reglas de enrutamiento condicionales. El valor de la variable de condición se almacena en los atributos de coincidencias y desajustes en MatchPair. El valor de la variable de condición se coloca en coincidencias que pueden coincidir , =y ,el valor !=de la variable de condición se coloca en desajustes que no pueden coincidir con la regla de enrutamiento. En. Durante el proceso de asignación, el código es relativamente elegante.

De hecho, las coincidencias y los desajustes almacenan valores variables de condición.

Condición Ruta # ruta

Router#routeEl papel es hacer coincidir el conjunto de Invoker que cumple con las reglas de enrutamiento.

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

复制代码

Como puede ver en el código anterior, siempre que el consumidor no tenga reglas coincidentes o el proveedor no tenga reglas coincidentes y force = false, no se devolverá el Invoker de los parámetros entrantes.

Los métodos para hacer coincidir las reglas de enrutamiento del consumidor y las reglas de enrutamiento del proveedor son matchWhen y matchThen

Ambos métodos coincidentes se implementan llamando al mismo método matchCondition. Convierta la URL del consumidor o proveedor en Mapa, y luego coincida con whenCondition o thenCondition.

Durante el proceso de coincidencia, si hay un valor correspondiente para la clave (es decir, el valor sampleValue), la coincidencia se realiza nuevamente mediante el método 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;
}
复制代码

El proceso de coincidencia se llama 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);
    }
}
复制代码

De esta manera, todas las reglas de enrutamiento condicional coinciden. Aunque el código parece ser más complicado, es mejor aclarar las reglas e ideas paso a paso. La premisa es estar familiarizado con el uso y la forma de los parámetros relacionados, de lo contrario, el código es más difícil de entender.

Pasado

Simplemente lógicamente, si puede dominar la implementación del enrutamiento condicional y estudiar otras formas de implementación del enrutamiento, creo que no habrá demasiados problemas. Solo por ejemplo, la implementación del enrutamiento de scripts, debe usar el motor de ejecución de scripts como premisa, de lo contrario no entenderá su código. Finalmente, el enrutamiento se puede configurar en dubbo-admin. Puede probar varias reglas de uso y comprender mejor y comprender la implementación del mecanismo de enrutamiento a través de la operación práctica.


Blog personal: ytao.top

Presta atención al número público [ytao], más buenos artículos originales

Mi cuenta publica

Supongo que te gusta

Origin juejin.im/post/5e94709cf265da47a83134b9
Recomendado
Clasificación