Basado en el código fuente, simule e implemente RabbitMQ: implementación de reglas de reenvío (6)

Tabla de contenido

1. Implementación de reglas de reenvío

1.1 Análisis de la demanda

1.2 Implementar reglas de reenvío del enrutador

1.2.1 Verificación de parámetros BindingKey y routeKey

1.2.2 Reglas de coincidencia de mensajes

1.2.3 Reglas de coincidencia de cambio de tema


1. Implementación de reglas de reenvío


1.1 Análisis de la demanda

Esto implementa principalmente la verificación de los parámetros routeKey y vinculanteKey, así como la implementación de reglas vinculantes de tipo TopicExchange.

Aquí nos centramos en las reglas de reenvío del cambio de tema.

clave vinculante: al crear un enlace, especifique una cadena especial para el enlace, lo que equivale a hacer una pregunta;

routeKey: al publicar un mensaje, la cadena especial especificada en el mensaje es equivalente a la respuesta;

Cuando la clave de ruta y la clave vinculante coinciden (la respuesta es correcta), el mensaje se puede reenviar a la cola correspondiente.

¿Qué significa poder igualar?

La composición de la clave de enrutamiento:

  1. Números, letras, guiones bajos.
  2. Utilice "." para dividir toda la clave de enrutamiento en varias partes

Por ejemplo:

  • aaa.bbb.ccc es legal
  • aaa.564.bbb es legal
  • aa legal

La composición de la clave vinculante:

  1. Números, letras, guiones bajos.
  2. Utilice "." para dividir toda la clave vinculante en varias partes
  3. Se admiten dos símbolos especiales como comodines (* y # deben ser partes separadas por .)
    1. * Puede coincidir con cualquier pieza independiente
    2. # Puede coincidir con 0 o más partes independientes

Por ejemplo:

  • aaa.*.bbb es legal
  • aaa.*.bbb es ilegal
  • aaa.#b.ccc Ilegal

Si se puede combinar, hay varias castañas de la siguiente manera:


Caso 1: No hay * y # en BindingKey. En este momento, RoutingKey y BindingKey deben ser exactamente iguales para que coincidan correctamente.

Supongamos clave vinculante: aaa.bbb.ccc

En este momento, la clave de ruta es la siguiente:

aaa.bbb.ccc (coincidencia exitosa)

aaa.cbb.ccc (coincidencia fallida)


Caso 2: BindingKey contiene * 

Supongamos clave vinculante: aaa.*.ccc

En este momento, la clave de ruta es la siguiente:

aaa.bbb.ccc (coincidencia exitosa)

aaa.gafdga.ccc (coincidencia exitosa)

aaa.bbb.eee.ccc (coincidencia fallida)


Caso 3: BindingKey contiene #

Supongamos clave vinculante: aaa.#.ccc

En este momento, la clave de ruta es la siguiente:

aaa.bbb.ccc (coincidencia exitosa)

aaa.bbb.ddd.ccc (coincidencia exitosa)

aaa.ccc (coincidencia exitosa)

aaa.b.ccc.d (coincidencia fallida)


Caso especial: si vinculanteKey se establece en #, todas las claves de enrutamiento pueden coincidir, de la siguiente manera

aaa

aaa.bbb

aaa.bbb.ccc

.......

En este punto, el cambio de tema es equivalente al cambio de distribución del usuario. 


Ps: las reglas anteriores están estipuladas por el protocolo AMQP

1.2 Implementar reglas de reenvío del enrutador

1.2.1 Verificación de parámetros BindingKey y routeKey

clave vinculante:

    * 1. Números, letras, guiones bajos
    * 2. Utilice . como delimitador
    * 3. * y # se permiten como caracteres comodín, pero los caracteres comodín solo se pueden utilizar como segmentos independientes

clave de enrutamiento:

     * 1. Letras, matrices, guiones bajos
     * 2. Utilice .para dividir

    /**
     * bindingKey 的构造规则
     * @param bindingKey
     * @return
     */
    public boolean checkBindingKey(String bindingKey) {
        if(bindingKey.length() == 0) {
            //空字符串,也是一种合法情况,比如使用 direct(routingKey 直接当作队列名字去匹配) / fanout 交换机的时候,bindingKey 是用不上的
            return true;
        }
        //检查字符串中不能存在的非法字符
        for(int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            if(ch >= 'A' && ch <= 'Z') {
                continue;
            }
            if(ch >= 'a' && ch <= 'z') {
                continue;
            }
            if(ch >= '0' && ch <= '9') {
                continue;
            }
            if(ch == '.' || ch == '_' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }
        //检查 * 或者 # 是否是独立的部分
        //aaa.*.bbb 是合法的;aaa.a*.bbb 是非法的
        String[] words = bindingKey.split("\\.");//这里是正则表达式
        for(String word : words) {
            //检查 word 长度 > 1,并且包含了 * 或者 # ,就是非法格式了
            if(word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }
        //约定一下,通配符之间的相邻关系(个人约定,不这样约定太难实现)
        //为什么这么约定。因为前三种相邻的时候,实现匹配的逻辑是非常繁琐,同时功能性提升不大
        //1. aaa.#.#.bbb  => 非法
        //2. aaa.#.*.bbb  => 非法
        //3. aaa.*.#.bbb  => 非法
        //4. aaa.*.*.bbb  => 合法
        for(int i = 0; i < words.length - 1; i++) {
            if(words[i].equals("#") && words[i + 1].equals("#")) {
                return false;
            }
            if(words[i].equals("#") && words[i + 1].equals("*")) {
                return false;
            }
            if(words[i].equals("*") && words[i + 1].equals("#")) {
                return false;
            }
        }
        return true;
    }

    /**
     * routingKey 的构造规则
     * @param routingKey
     * @return
     */
    public boolean checkRoutingKey(String routingKey) {
        if(routingKey.length() == 0) {
            //空字符串是合法情况,比如使用 faout 交换机的时候,routingKey 用不上,就可以设置为 “”
            return true;
        }
        for(int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            // 检查大写字母
            if(ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 检查小写字母
            if(ch >= 'a' && ch <= 'z') {
                continue;
            }
            //检查数字
            if(ch >= '0' && ch <= '9') {
                continue;
            }
            //检查下划线和.
            if(ch == '_' || ch == '.') {
                continue;
            }
            //不是上述规则,就是错误
            return false;
        }
        return true;
    }

Ps: El parámetro en el método de división es una expresión regular ~ En primer lugar, "." es un símbolo especial en las expresiones regulares. "\\." es para hacer coincidir . con el texto original. Desea utilizar . como original texto. El texto debe representarse mediante \. en la expresión regular, y debido a que en las cadenas de Java, "\" es un carácter especial y debe escaparse usando "\\". En este momento, el método "\\." en realidad ingrese el texto "."

(Está bien si no entiendes esto, solo recuérdalo)

1.2.2 Reglas de coincidencia de mensajes

La lógica de los conmutadores directos y los conmutadores de distribución se ha implementado en detalle antes. Aquí nos centraremos principalmente en los conmutadores temáticos.

    /**
     * 判定消息是否可以转发给这个绑定的队列
     * @param exchangeType
     * @param binding
     * @param message
     * @return
     */
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        //1.判断当前交换机类型
        if(exchangeType == ExchangeType.FANOUT) {
            // 扇出交换机,要给每一个绑定的队列都发送消息
            return true;
        } else if(exchangeType == ExchangeType.TOPIC) {
            // 主题交换机
            return routeTopic(binding, message);
        } else {
            // 其他情况是不存在的
            throw new MqException("[Router] 交换机类型非法!exchangeType=" + exchangeType);
        }
    }

1.2.3 Reglas de coincidencia de cambio de tema

Supongamos clave vinculante: aaa.*.bbb.#.ccc

Supongamos clave de enrutamiento: aaa.11.bbb.22.33.ccc

Aquí utilizamos el algoritmo de doble puntero para hacer coincidir:

  1. Primero use split para dividir estas dos cadenas mediante ".".
  2. Si apunta a una cadena normal, debe ser exactamente igual al contenido ejecutado por el subíndice correspondiente de routeKey.
  3. Si apunta a *, no importa a qué apunte la clave de ruta, ambas partes avanzarán el subíndice al mismo tiempo.
  4. Cuando se encuentra #, y si no hay otro contenido después de #, se devolverá verdadero directamente y la coincidencia será exitosa.
  5. Cuando se encuentra # y hay contenido después de #, tome el contenido después de #, búsquelo en RoutingKey y busque la siguiente parte, donde aparece en RoutingKey (si la última parte no existe en RoutingKey, la coincidencia se considerará fallido. ).
  6.  Si ambos punteros llegan al final al mismo tiempo, la coincidencia tiene éxito; de lo contrario, la coincidencia falla.

    public boolean routeTopic(Binding binding, Message message) {
        //先进行切分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");

        //使用双指针
        int bindingIndex = 0;
        int routingIndex = 0;
        while(bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
            if(bindingTokens[bindingIndex].equals("*")) {
                //遇到 * ,继续向后走
                bindingIndex++;
                routingIndex++;
            } else if(bindingTokens[bindingIndex].equals("#")) {
                //如果遇到 #,先看看还有没有下一个位置
                bindingIndex++;
                if(bindingIndex == bindingTokens.length) {
                    //后面没有东西,一定匹配成功
                    return true;
                }
                //如果后面还有东西,就去 routingKey 后面的位置去找
                //findNextMatch 这个方法用来查找该部分再 routingKey 的位置,返回该下标,没找到,就返回 -1
                routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
                if(routingIndex == -1) {
                    //后面没有匹配结果,失败
                    return false;
                }
                //找到了,就继续往后匹配
                routingIndex++;
                bindingIndex++;
            } else {
                //如果遇到普通字符串,要求两边的内容是一样的
                if(!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
        //判定是否双方同时到达末尾
        //比如 aaa.bbb.ccc 和 aaa.bbb 是要匹配失败的
        return (bindingIndex == bindingTokens.length && routingIndex == routingTokens.length);
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for(int i = routingIndex; i < routingTokens.length; i++) {
            if(routingTokens[i].equals(bindingToken)) {
                return i;
            }
        }
        return -1;
    }

 

Supongo que te gusta

Origin blog.csdn.net/CYK_byte/article/details/132411602
Recomendado
Clasificación