根据源码,模拟实现 RabbitMQ - 转发规则实现(6)

目录

一、转发规则实现

1.1、需求分析

1.2、实现 Router 转发规则

1.2.1、bindingKey 和 routingKey 参数校验

1.2.2、消息匹配规则

1.2.3、主题交换机匹配规则


一、转发规则实现


1.1、需求分析

这里主要实现 routingKey 和 bindingKey 参数的校验,以及 TopicExchange 类型绑定规则的实现.

这里重点来看一下 Topic 交换机的转发规则

bindingkey:创建绑定的时候,给绑定指定特殊字符串,相当于出题;

routingKey:发布消息的时候,给消息上指定的特殊字符串,相当于做答案;

当 routingKey 和 bindingKey 匹配(答案对的上),就可以将消息转发给对应的队列。

什么叫做能匹配的上?

routingKey 的组成:

  1. 数字、字母、下划线
  2. 使用 "." 把整个routingKey 分成多个部分

例如:

  • aaa.bbb.ccc        合法
  • aaa.564.bbb       合法
  • aaa                     合法

bindingKey 的组成:

  1. 数字、字母、下划线
  2. 使用 "." 把整个 bindingKey 分成多个部分
  3. 支持两种特殊的符号作为通配符(* 和 # 必须是作为被 . 分割出来的独立部分)
    1. * 可以匹配任何一个独立的部分
    2. # 可以匹配任何 0 个或者多个独立的部分

例如:

  • aaa.*.bbb    合法
  • aaa.*.bbb    非法
  • aaa.#b.ccc  非法

是否能匹配上,有如下几个栗子:


情况一:bindingKey 中没有 * 和 # ,此时,必须要求 routingKey 和 bindingKey 一模一样,才能匹配成功

假设 bindingKey:aaa.bbb.ccc

此时 routingKey 如下:

aaa.bbb.ccc (匹配成功)

aaa.cbb.ccc (匹配失败)


情况二: bindingKey 中有 * 

假设 bindingKey:aaa.*.ccc

此时 routingKey 如下:

aaa.bbb.ccc  (匹配成功)

aaa.gafdga.ccc  (匹配成功)

aaa.bbb.eee.ccc (匹配失败)


情况三:bindingKey 中有 #

假设 bindingKey:aaa.#.ccc

此时 routingKey 如下:

aaa.bbb.ccc   (匹配成功)

aaa.bbb.ddd.ccc   (匹配成功)

aaa.ccc(匹配成功)

aaa.b.ccc.d(匹配失败)


特殊情况:如果把 bindingKey 设置成 #,就可以匹配到所有 routingKey,如下

aaa

aaa.bbb

aaa.bbb.ccc

.......

此时,topic 交换机就相当用户 fanout 交换机了 


Ps:上述规则是 AMQP 协议约定的

1.2、实现 Router 转发规则

1.2.1、bindingKey 和 routingKey 参数校验

bindingKey:

    * 1.数字、字母、下划线
    * 2.使用 . 作为分隔符
    * 3.允许存在 * 和 # 作为通配符,但是通配符只能作为独立的分段

routingKey:

     * 1.字母、数组、下划线
     * 2.使用 . 进行分割

    /**
     * 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:split 方法中的参数是一个正则表达式~  首先 "."  在正则表达式中是一个特殊的符号,"\\." 是把 . 当作原始文本进行匹配,想要使用 . 作为为原始文本,就需要在正则表达式中使用 \. 来表示,又因为,在 Java 的字符串中,"\" 是一个特殊字符,需要使用 "\\" 来转义,此时才能通过 "\\." 的方式真正录入 "." 这个文本

(这个不懂没关系,记住就行了)

1.2.2、消息匹配规则

之前已经具体实现了 直接交换机和扇出交换机的逻辑了,这里主要关注主题交换机.

    /**
     * 判定消息是否可以转发给这个绑定的队列
     * @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、主题交换机匹配规则

假设 bindingKey:aaa.*.bbb.#.ccc

假设 routingKey:aaa.11.bbb.22.33.ccc

这里我们使用双指针算法进行匹配:

  1. 先将这两个字符串使用 split 通过 "." 进行分割.
  2. 如果指向的是一个普通的字符串,此时要求和 routingKey 的对应下标执行的内容完全一致.、
  3. 如果指向的是 *,此时无论 routingKey 指向的是什么,双方都同时下标前进.
  4. 遇到 # 了,并且如果 # 后面没有其他内容了,直接返回 true,匹配成功.
  5. 遇到 # 了,并且 # 后面还有内容,拿着 # 后面的一段内容,去 routingKey 中查找,找到后面的部分,在 routingKey 中出现的位置(如果后面部分在 routingKey 中不存在,直接认为匹配失败).
  6.  两个指针同时到达末尾,则匹配成功,反之匹配失败

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/88647.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构基础:P3-树(上)----编程作业02:List Leaves

本系列文章为浙江大学陈越、何钦铭数据结构学习笔记&#xff0c;系列文章链接如下&#xff1a; 数据结构(陈越、何钦铭)学习笔记 文章目录 一、题目描述二、整体思路与实现代码 一、题目描述 题目描述&#xff1a; 给定一棵树&#xff0c;按照从上到下、从左到右的顺序列出所有…

正则表达式一小时学完

闯关式学习Regex 正则表达式&#xff0c;我感觉挺不错的&#xff0c;记录一下。 遇到不会的题&#xff0c;可以评论交流。 真的很不错 链接 Regex Learn - Step by step, from zero to advanced.

搜索树基础:二叉搜索树(详解特性用途,图解实现过程)

二叉搜索树 二叉搜索树的特性二叉搜索树的主要用途二叉搜索树的基本操作1、二叉搜索树的查找2、二叉搜索树的插入3、二叉搜索树的删除&#xff08;难点&#xff09;&#xff08;1&#xff09;找到待删结点&#xff08;2&#xff09;分情况删除 二叉搜索树的特性 二叉搜索树又称…

升级还是不升级?iPhone 15和iPhone 14 Plus性能比较

预览iPhone 15 Pro Max与三星Galaxy S23 Ultra之战是有正当理由的。显然,三星的旗舰智能手机为2023年的所有其他旗舰产品定下了基调——由于其超长的电池寿命和一流的摄像头,证明了它是最受欢迎的产品。 毫不奇怪,Galaxy S23 Ultra不仅是最好的照相手机之一,也是花钱能买到…

【JVM基础】JVM入门基础

目录 JVM的位置三种 JVMJVM体系结构类加载器双亲委派机制概念例子作用 沙箱安全机制组成沙箱的基本组件 NativeJNI&#xff1a;Java Native Interface&#xff08;本地方法接口&#xff09;Native Method Stack&#xff08;本地方法栈&#xff09; PC寄存器&#xff08;Program…

自动驾驶SLAM技术第四章习题2

在g2o的基础上改成ceres优化&#xff0c;高博都写好了其他的部分, 后面改ceres就很简单了. 这块我用的是ceres的自动求导&#xff0c;很方便&#xff0c;就是转化为模板仿函数的时候有点麻烦&#xff0c; 代码部分如下 ceres_type.h : ceres优化核心库的头文件 这个文件写的内…

openGauss学习笔记-48 openGauss 高级数据管理-函数

文章目录 openGauss学习笔记-48 openGauss 高级数据管理-函数48.1 数学函数48.2 三角函数列表48.3 字符串函数和操作符48.4 类型转换相关函数 openGauss学习笔记-48 openGauss 高级数据管理-函数 openGauss常用的函数如下&#xff1a; 48.1 数学函数 abs(x) 描述&#xff1a;…

IDEA中使用Docker插件构建镜像并推送至私服Harbor

一、开启Docker服务器的远程访问 1.1 开启2375远程访问 默认的dokcer是不支持远程访问的&#xff0c;需要加点配置&#xff0c;开启Docker的远程访问 # 首先查看docker配置文件所在位置 systemctl status docker# 会输出如下内容&#xff1a; ● docker.service - Docker Ap…

测试框架pytest教程(10)自定义命令行-pytest_addoption

pytest_addoption pytest_addoption是pytest插件系统中的一个钩子函数&#xff0c;用于向pytest添加自定义命令行选项。 在pytest中&#xff0c;可以使用命令行选项来控制测试的行为和配置。pytest_addoption钩子函数允许您在运行pytest时添加自定义的命令行选项&#xff0c;…

【VS Code插件开发】消息通信(四)

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;前端领域优质作者、阿里云专家博主&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &#x1f4e2; 资料领取&#xff1a;前端…

adb 命令

1.adb shell dumpsys activity top | find "ACTIVITY" 查看当前运行的activity包名 2.adb shell am start -n 包名/页面名 打开应用的页面 3.查看将要启动或退出app的包名 adb shell am monitor 只有在启动或退出的时候才会打印 4.查看当前启动应用的包名 ad…

AR室内导航技术之技术说明与效果展示

随着科技的飞速发展&#xff0c;我们周围的环境正在经历着一场数字化的革命。其中&#xff0c;AR室内导航技术以其独特的魅力&#xff0c;为我们打开了一扇通往全新数字化世界的大门。本文将为您详细介绍这一技术的实现原理、工具应用以及成品展示&#xff0c;带您领略AR室内导…

mybatis-plus--配置-(sql)日志输出-自动填充-分页-多数据源-逻辑删除

写在前面&#xff1a; 本文主要介绍mybatis-plus的配置&#xff0c;以后在有的时候在补充。欢迎交流。 文章目录 日志输出自动填充分页全局字段配置多数据源 日志输出 调试的时候需要看执行的sql&#xff0c;这时候就很需要日志来记录查看了。 mybatis-plus的日志配置在yml…

自动化部署及监测平台基本架构

声明 本文是学习 政务计算机终端核心配置规范. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 核心配置自动化部署及监测技术要求 自动化部署及监测平台基本架构 对于有一定规模的政务终端核心配置应用&#xff0c;需要配备自动化部署及监测平台&am…

01-jupyter notebook的使用方法

一、Tab补全 在shell中输入表达式&#xff0c;按下Tab&#xff0c;会搜索已输入变量&#xff08;对象、函数等等&#xff09;的命名空间&#xff1a; 除了补全命名、对象和模块属性&#xff0c;Tab还可以补全其它的。当输入看似文件路径时 &#xff08;即使是Python字符串&…

浅拷贝与深拷贝

作者简介&#xff1a; zoro-1&#xff0c;目前大一&#xff0c;正在学习Java&#xff0c;数据结构等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 浅拷贝与深拷贝 浅拷贝浅拷贝定义浅拷贝代码演示浅…

优秀产品奖!移远5G RedCap模组,让5G真正“轻”下来

8月24日&#xff0c;在通信世界全媒体主办的“5G RedCap技术与物联网应用创新研讨会”上&#xff0c;“5G RedCap优秀产品和解决方案”获奖名单发布&#xff0c;移远通信5G RedCap模组Rx255C系列以其在创新性、实用性、经济性、成熟性等方面的综合领先优势&#xff0c;获此殊荣…

更高效稳定 | 基于ACM32 MCU的编程直流电源应用方案

随着电子设备的多样化发展&#xff0c;面对不同的应用场景&#xff0c;需要采用特定的供电电源。因此&#xff0c;在电子产品的开发测试过程中&#xff0c;必不可少使用编程直流电源来提供测试电压&#xff0c;协助完成初步的开发测试过程。 编程直流电源概述 编程直流电源结构…

李沐pytorch学习-经典CNN的原理及代码实现

一、LeNet 1.1 模型结构 LeNet结构如图1所示&#xff0c;汇聚层即池化层&#xff0c;这里池化Stride&#xff08;步幅&#xff09;与池化层长宽一致&#xff0c;因此使得池化后大小减半。 图1. LeNet结构 1.2 代码实现 代码实现如下&#xff1a; import torch from torch imp…

Arnold置乱

一、Arnold置乱概述 Arnold变换是俄国数学家弗拉基米尔阿诺德&#xff08;Vladimir Igorevich Arnold&#xff09;提出&#xff0c;Arnold将其应用在遍历理论研究中。由于Arnold本人最初对一张猫的图片进行了此种变换&#xff0c;因此它又被称为猫脸变换&#xff08;cat映射&am…