【消息队列开发】 实现Router类——交换机的转发规则

news2024/9/23 11:24:50

文章目录

  • 🍃前言
  • 🍀判断routingKey是否合法
  • 🎄判断bindingKey是否合法
  • 🌴判断bindingKey 与 routingKey 是否匹配
  • 🌲测试匹配方法
  • ⭕总结

🍃前言

本次开发任务

  • 实现Router类, 使用这个类, 来实现交换机的转发规则.
  • 同时也借助这个类验证 bindingKey 与 routingKey 是否合法。

在这里插入图片描述

🍀判断routingKey是否合法

routingKey:发布消息的时候,给消息上指定的特殊字符串,与bindingKey 做匹配的。

如果 rontingKey 是答案,那么bindingKey 就是题目

对于routingKey 的命名,我们做出以下规定

  1. 由数字、字母、下划线组成
  2. 使用.把整个 routingKey 分成多个部分
    形如:aaa.bbb.cc

根据以上规定,我们只需要对传入的 routingKey 进行一个一个字符进行判断即可。

如果符合上述规定,则 continue ,继续循环。若不符合,返回false即可

代码实现如下:

// routingKey 的构造规则:
// 1. 数字, 字母, 下划线
// 2. 使用 . 分割成若干部分
public boolean checkRoutingKey(String routingKey) {
    if (routingKey.length() == 0) {
        // 空字符串. 合法的情况. 比如在使用 fanout 交换机的时候, 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;
        }
        // 该字符, 不是上述任何一种合法情况, 就直接返回 false
        return false;
    }
    // 把每个字符都检查过, 没有遇到非法情况. 此时直接返回 true
    return true;
}

🎄判断bindingKey是否合法

bindingKey:创建绑定的时候,给绑定指定的特殊字符串。

bindingKey 的命名规则如下:

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

在这里插入图片描述

此外,为了后面方便匹配,博主这里规定,通配符之间有如下规定:

  1. aaa.#.#.bbb => 非法
  2. aaa.#.*.bbb => 非法
  3. aaa.*.#.bbb => 非法
  4. aaa...bbb => 合法

根据以上规定,我们首先先对传进来的 bindingKey 的每一个字符进行判断,若不和上述规定,直接返回false 即可;

若都符合,我们就以.将bindingKey 分为几个独立的部分,用数组进行接收,

然后我们进行判断,如果每个独立部分,长度大于1且含有*或者#,我们就说他是非法的

最后就是相邻的两个独立部分不能是:

  1. ##
  2. *#
  3. #*

代码实现如下:

// bindingKey 的构造规则:
// 1. 数字, 字母, 下划线
// 2. 使用 . 分割成若干部分
// 3. 允许存在 * 和 # 作为通配符. 但是通配符只能作为独立的分段.
public boolean checkBindingKey(String bindingKey) {
    if (bindingKey.length() == 0) {
        // 空字符串, 也是合法情况. 比如在使用 direct / 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;
}

🌴判断bindingKey 与 routingKey 是否匹配

我们首先将 bindingKey 与 routingKey 进行切分,用数组进行接收。

引入两个下标, 指向上述两个数组. 初始情况下都为 0

然后进行一一比对,我们分为五种情况进行判断:

  • 情况一:如果遇到普通字符串(不是*,也不是#), 要求两边的内容是一样的.
    在这里插入图片描述
  • 情况二:如果遇到 * , 直接进入下一轮. * 可以匹配到任意一个部分!!
    在这里插入图片描述
  • 情况三:遇到#,该 # 后面没东西了, 说明此时一定能匹配成功了!
    在这里插入图片描述
  • 情况四:# 后面还有东西, 拿着后面的内容, 去 routingKey 中往后找, 找到对应的位置.
    在这里插入图片描述
  • 情况五:判定是否是双方同时到达末尾
    在这里插入图片描述

根据上述五种情况,代码书写如下:

// 这个方法用来判定该消息是否可以转发给这个绑定对应的队列.
public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
    // 根据不同的 exchangeType 使用不同的判定转发规则.
    if (exchangeType == ExchangeType.FANOUT) {
        // 如果是 FANOUT 类型, 则该交换机上绑定的所有队列都需要转发
        return true;
    } else if (exchangeType == ExchangeType.TOPIC) {
        // 如果是 TOPIC 主题交换机, 规则就要更复杂一些.
        return routeTopic(binding, message);
    } else {
        // 其他情况是不应该存在的.
        throw new MqException("[Router] 交换机类型非法! exchangeType=" + exchangeType);
    }
}


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

    // 引入两个下标, 指向上述两个数组. 初始情况下都为 0
    int bindingIndex = 0;
    int routingIndex = 0;
    // 此处使用 while 更合适, 每次循环, 下标不一定就是 + 1, 不适合使用 for
    while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
        if (bindingTokens[bindingIndex].equals("*")) {
            // [情况二] 如果遇到 * , 直接进入下一轮. * 可以匹配到任意一个部分!!
            bindingIndex++;
            routingIndex++;
            continue;
        } 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;
            }
            // 找到的匹配的情况, 继续往后匹配.
            bindingIndex++;
            routingIndex++;
        } else {
            // [情况一] 如果遇到普通字符串, 要求两边的内容是一样的.
            if (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                return false;
            }
            bindingIndex++;
            routingIndex++;
        }
    }
    // [情况五] 判定是否是双方同时到达末尾
    // 比如 aaa.bbb.ccc  和  aaa.bbb 是要匹配失败的.
    if (bindingIndex == bindingTokens.length && routingIndex == routingTokens.length) {
        return true;
    }
    return false;
}

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

🌲测试匹配方法

我们对上述所书写的匹配方法进行测试一下。

测试数据如下:
在这里插入图片描述
测试代码如下:
在这里插入图片描述

@SpringBootTest
public class RouterTests {
    private Router router = new Router();
    private Binding binding = null;
    private Message message = null;

    @BeforeEach
    public void setUp() {
        binding = new Binding();
        message = new Message();
    }

    @AfterEach
    public void tearDown() {
        binding = null;
        message = null;
    }

    // [测试用例]
    // binding key          routing key         result
    // aaa                  aaa                 true
    // aaa.bbb              aaa.bbb             true
    // aaa.bbb              aaa.bbb.ccc         false
    // aaa.bbb              aaa.ccc             false
    // aaa.bbb.ccc          aaa.bbb.ccc         true
    // aaa.*                aaa.bbb             true
    // aaa.*.bbb            aaa.bbb.ccc         false
    // *.aaa.bbb            aaa.bbb             false
    // #                    aaa.bbb.ccc         true
    // aaa.#                aaa.bbb             true
    // aaa.#                aaa.bbb.ccc         true
    // aaa.#.ccc            aaa.ccc             true
    // aaa.#.ccc            aaa.bbb.ccc         true
    // aaa.#.ccc            aaa.aaa.bbb.ccc     true
    // #.ccc                ccc                 true
    // #.ccc                aaa.bbb.ccc         true
    @Test
    public void test1() throws MqException {
        binding.setBindingKey("aaa");
        message.setRoutingKey("aaa");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test2() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test3() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test4() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.ccc");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test5() throws MqException {
        binding.setBindingKey("aaa.bbb.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test6() throws MqException {
        binding.setBindingKey("aaa.*");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test7() throws MqException {
        binding.setBindingKey("aaa.*.bbb");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test8() throws MqException {
        binding.setBindingKey("*.aaa.bbb");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test9() throws MqException {
        binding.setBindingKey("#");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test10() throws MqException {
        binding.setBindingKey("aaa.#");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test11() throws MqException {
        binding.setBindingKey("aaa.#");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test12() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test13() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test14() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test15() throws MqException {
        binding.setBindingKey("#.ccc");
        message.setRoutingKey("ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }

    @Test
    public void test16() throws MqException {
        binding.setBindingKey("#.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC, binding, message));
    }
}

测试结果如下:
在这里插入图片描述

⭕总结

关于《【消息队列开发】 实现Router类——交换机的转发规则》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【No.12】蓝桥杯可撤销并查集|查找|合并|撤销(C++)

前置知识 蓝桥杯并查集|路径压缩|合并优化|按秩合并|合根植物(C)-CSDN博客 可撤销并查集 关键注意 可撤销并查集的撤销功能如何实现可撤销并查集能不能用路径压缩 可撤销并查集(Reversible Union-Find)是一种扩展了标准并查集(Union-Find)数据结构的数据结构&#xff0c;它允…

你要的个性化生信分析服务今天正式开启啦!定制你的专属解决方案!全程1v1答疑!

之前在 干货满满 | 给生信小白的入门小建议 | 掏心掏肺版 中有提到&#xff0c;如果小伙伴们真的想学好生信&#xff0c;那编程能力是必须要有的&#xff01;但是可能有些小伙伴们并没有那么多的时间从头开始学习编程&#xff0c;又或是希望有人指导或者协助完成生信分析工作&a…

数据库原理及应用期末+考研复试

文章目录 一、数据库系统概述二、数据模型2.1E-R数据模型2.2层次数据模型2.3网状数据模型2.4关系数据模型 三、数据库系统的体系结构3.1数据库系统体系结构3.2数据库系统3.3数据库管理系统 四、关系数据库结构化查询语言——SQL语言4.1基本表定义4.2查询结果显示4.3查询满足条件…

【JDBC编程】 Java程序操作数据库

目录 一、数据库编程的必备条件 二、什么是JDBC&#xff1f; 三、JDBC的使用 1. 准备工作 2. 建立数据库连接 2.1 加载驱动程序 2.2 数据库连接池技术 3. 正式操作 四、JDBC的局限性与MyBatis的优势 一、数据库编程的必备条件 编程语言&#xff0c;如Java&#xff0…

揭秘爆红AI图像增强神器:Magnific AI如何做到1亿像素放大?

最近有个很火的AI图像增强应用&#xff0c;叫Magnific AI。 你知道吗&#xff0c;它发布一个多月就有40万人注册了&#xff01; 这个应用确实非常实用&#xff0c;它不仅利用AI技术放大了图像&#xff0c;还能提升分辨率&#xff0c;从而使图片呈现得更加清晰。 值得一提的是…

观后感-华为中国合作伙伴大会2024

非常荣幸能够参加华为中国合作伙伴大会 2024。以往我参加的会议多数是由政府单独举办的&#xff0c;然而这次进入会场后&#xff0c;我第一感觉就是无比震撼。我从未想过一家企业竟能举办如此规模宏大的会议&#xff0c;当我签到的时候才发现&#xff0c;自己已经是两万多名参会…

环境变量配置

举一个小例子来演示一下环境变量配置。 在CMD中打开QQ界面&#xff0c;首先需要知道QQ.exe文件的完整路径。一旦有了这个路径&#xff0c;可以按照以下步骤操作&#xff1a; 打开CMD窗口。可以通过按下Windows键R&#xff0c;输入“cmd”并回车来打开它。在CMD窗口中&#xf…

2024年语言艺术、人文发展与教育国际会议(ICLAHDE2024)

2024年文学、历史与艺术设计国际会议(ICLHAD2024) 一、【会议简介】 2024年国际语言艺术、人文发展与教育会议&#xff08;ICLAHDE2024&#xff09;将在中国昆明举行&#xff0c;主题为“语言、人文与艺术”。ICLAHDE汇集了来自世界各地语言艺术、人类发展和教育领域的学者、工…

图书推荐|图解算法:C语言实现+视频教学版

零负担理解数据结构及其算法的设计&#xff0c;零基础也能快速上手编程。 本书内容 《图解算法&#xff1a;C语言实现视频教学版》是一本综合讲述数据结构及其算法的入门书&#xff0c;力求简洁、清晰、严谨、且易于学习和掌握。 《图解算法&#xff1a;C语言实现视频教学版》…

【系统架构师】-第6章-数据库设计基础知识

1、三级模式-两级映像 外模式&#xff1a;视图、用户与数据库的接口 概念模式&#xff1a;表 内模式&#xff1a;存储方式&#xff0c;索引创建等 1&#xff09;外模式-模式映射&#xff1a; 视图与表的映射&#xff0c;表数据发生修改&#xff0c;只需要修改映射&#xf…

淘宝店铺如何从1688一键铺货?官方授权API接口,可满足多样化上货需求

那么新手卖家如何将1688的源头厂货一键铺货到淘宝店铺呢&#xff1f;下面我教大家几招&#xff1a; 1、通过淘宝复制一键复制上货 淘宝API接口采集 taobao.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretStr…

销售数据分析怎么做?用好这5个数据分析方法与模型就足够了。

企业经营其实简单来说就是做买卖&#xff0c;有了买卖自然就产生了销售数据&#xff0c;那怎么能让这些销售数据产生价值呢&#xff1f;答案就是数据分析。通过对销售数据的分析&#xff0c;可以帮助企业及时洞察市场动向&#xff0c;发现企业销售过程中的问题&#xff0c;调整…

用户行为分析是什么?为什么我们需要 bitmap?

本文非常好&#xff1a;https://blog.bcmeng.com/post/doris-bitmap.html meta搜也非常好&#xff1a;https://metaso.cn/ 用户行为分析是什么&#xff1f;简单说&#xff0c;就是围绕全体用户&#xff0c;做各种分析。用户就是一个个的 id。id 在不同方面有各种行为记录&…

c语言--字符转换函数(tolower、toupper.)

目录 一、前言二、使用举例 一、前言 C语⾔提供了2个字符转换函数&#xff1a; int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写 int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写二、使用举例 #include <ctype.h> #include<stdio.h> int main(…

母亲的奶牛(蓝桥杯,acwing每日一题)

题目描述&#xff1a; 农夫约翰有三个容量分别为 A,B,C升的挤奶桶。 最开始桶 A 和桶 B 都是空的&#xff0c;而桶 C 里装满了牛奶。 有时&#xff0c;约翰会将牛奶从一个桶倒到另一个桶中&#xff0c;直到被倒入牛奶的桶满了或者倒出牛奶的桶空了为止。 这一过程中间不能有…

LeetCode刷题记录:(12)全排列2

leetcode传送通道 class Solution {List<List<Integer>> result new ArrayList<>();List<Integer> path new ArrayList<>();boolean[] used;public List<List<Integer>> permuteUnique(int[] nums) {used new boolean[nums.lengt…

Java代码基础算法练习-求一个三位数的各位平方之和-2024.03.21

任务描述&#xff1a; 输入一个正整数n&#xff08;取值范围&#xff1a;100<n<1000&#xff09;&#xff0c;然后输出每位数字的平方和。 任务要求&#xff1a; 代码示例&#xff1a; package march0317_0331;import java.util.Scanner;public class m240321 {public …

OpenCV4.9.0开源计算机视觉库安装教程

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 引言&#xff1a;OpenCV系列文章中的安装部分今天全部完成了&#xff0c;为了读者更方便阅读&#xff0c;大家可以按下列索引前往&#xff0c;成文较为仓促有错漏在所难免&#xff0c;欢迎大家指正…

CircuitBreaker熔断器

CircuitBreaker熔断器 1、Hystrix目前也进入维护模式 ​ Hystrix是一个用于处理分布式系统的延迟和容错的开源库&#xff0c;在分布式系统里&#xff0c;许多依赖不可避免的会调用失败&#xff0c;比如超时、异常等&#xff0c;Hystrix能够保证在一个依赖出问题的情况下&…

Uibot6.0 (RPA财务机器人师资培训第2天 )采购付款——网银付款机器人案例实战

训练网站&#xff1a;泓江科技 (lessonplan.cn)https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981(本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff0…