Based on the source code, simulate and implement RabbitMQ - forwarding rule implementation (6)

Table of contents

1. Implementation of forwarding rules

1.1. Demand analysis

1.2. Implement Router forwarding rules

1.2.1. BindingKey and routingKey parameter verification

1.2.2. Message matching rules

1.2.3. Topic switch matching rules


1. Implementation of forwarding rules


1.1. Demand analysis

This mainly implements the verification of routingKey and bindingKey parameters, as well as the implementation of TopicExchange type binding rules.

Here we focus on the forwarding rules of the Topic switch.

bindingkey: When creating a binding, specify a special string for the binding, which is equivalent to asking a question;

routingKey: When publishing a message, the special string specified on the message is equivalent to the answer;

When routingKey and bindingKey match (the answer is correct), the message can be forwarded to the corresponding queue.

What does it mean to be able to match?

The composition of routingKey:

  1. Numbers, letters, underscores
  2. Use "." to divide the entire routingKey into multiple parts

For example:

  • aaa.bbb.ccc is legal
  • aaa.564.bbb is legal
  • aaa legal

The composition of bindingKey:

  1. Numbers, letters, underscores
  2. Use "." to divide the entire bindingKey into multiple parts
  3. Two special symbols are supported as wildcards (* and # must be separate parts separated by .)
    1. * Can match any independent part
    2. # Can match any 0 or more independent parts

For example:

  • aaa.*.bbb is legal
  • aaa.*.bbb is illegal
  • aaa.#b.ccc Illegal

Whether it can be matched, there are several chestnuts as follows:


Case 1: There are no * and # in bindingKey. At this time, routingKey and bindingKey must be exactly the same to match successfully.

Assume bindingKey: aaa.bbb.ccc

At this time routingKey is as follows:

aaa.bbb.ccc (matched successfully)

aaa.cbb.ccc (match failed)


Case 2: BindingKey contains * 

Assume bindingKey: aaa.*.ccc

At this time routingKey is as follows:

aaa.bbb.ccc (matched successfully)

aaa.gafdga.ccc (matched successfully)

aaa.bbb.eee.ccc (match failed)


Case 3: BindingKey contains #

Assume bindingKey: aaa.#.ccc

At this time routingKey is as follows:

aaa.bbb.ccc (matched successfully)

aaa.bbb.ddd.ccc (matched successfully)

aaa.ccc (matched successfully)

aaa.b.ccc.d (match failed)


Special case: If bindingKey is set to #, all routingKeys can be matched, as follows

aaa

aaa.bbb

aaa.bbb.ccc

.......

At this point, the topic switch is equivalent to the user fanout switch. 


Ps: The above rules are stipulated by the AMQP protocol

1.2. Implement Router forwarding rules

1.2.1. BindingKey and routingKey parameter verification

bindingKey:

    * 1. Numbers, letters, underscores
    * 2. Use . as delimiter
    * 3. * and # are allowed as wildcard characters, but wildcard characters can only be used as independent segments

routingKey:

     * 1. Letters, arrays, underscores
     * 2. Use . to divide

    /**
     * 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: The parameter in the split method is a regular expression~ First of all, "." is a special symbol in regular expressions. "\\." is to match . as the original text. You want to use . as the original text. Text needs to be represented by \. in the regular expression, and because in Java strings, "\" is a special character and needs to be escaped using "\\". At this time, "\\ ." method to actually enter the text "."

(It’s okay if you don’t understand this, just remember it)

1.2.2. Message matching rules

The logic of direct switches and fan-out switches has been implemented in detail before. Here we focus mainly on topic switches.

    /**
     * 判定消息是否可以转发给这个绑定的队列
     * @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. Topic switch matching rules

Assume bindingKey: aaa.*.bbb.#.ccc

Assume routingKey: aaa.11.bbb.22.33.ccc

Here we use the double pointer algorithm for matching:

  1. First use split to split these two strings through ".".
  2. If it points to an ordinary string, it must be exactly the same as the content executed by the corresponding subscript of routingKey.
  3. If it points to *, no matter what the routingKey points to, both parties will advance the subscript at the same time.
  4. When # is encountered, and if there is no other content after #, true will be returned directly, and the match is successful.
  5. When # is encountered, and there is content after #, take the piece of content after #, search it in routingKey, and find the following part, where it appears in routingKey (if the latter part does not exist in routingKey, the match will be considered failed. ).
  6.  If both pointers reach the end at the same time, the match is successful, otherwise the match fails.

    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;
    }

 

Guess you like

Origin blog.csdn.net/CYK_byte/article/details/132411602