2024.2.21 模拟实现 RabbitMQ —— 实现转发规则

目录

需求分析

直接交换机(Direct )

主题交换机(Topic )

扇出交换机(Fanout )

Topic 交换机转发规则

routingKey 组成

bindingKey 组成

匹配规则

情况一

情况二

情况三

实现 Router 类

校验 bindingKey 和 routingKey

消息匹配机制

Topic 交换机匹配规则

针对 Router 单元测试


需求分析

直接交换机(Direct )

  • 通过设定 消息的 routingKey = 队列名,以此指定该消息需传给哪个队列

实例理解

  • 如下图所示,此时可直接无视绑定关系,直接从内存中拿到对应队列名的队列
  • 然后再将消息传给该队列即可


主题交换机(Topic )

  • 依据 Topic 交换机的转发规则,判定消息需传给哪些队列

扇出交换机(Fanout )

  • 给该交换机中所有绑定好的队列均传入消息

Topic 交换机转发规则

  • bindingKey:创建绑定时,给绑定指定的特殊字符串(相当于出题)
  • routingKey:发布消息时,给消息上指定的特殊字符串(相当于做答案)
  • 当 routingKey 的答案能够与 bindingKey 相对应时,便可将消息转发给该队列 

routingKey 组成

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

实例理解

  •  aaa.bbb.ccc    合法
  • aaa.110.bbb     合法
  • aaa                   合法

bindingKey 组成

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

实例理解

  • aaa.*.bbb       合法
  • aaa.*bb.ccc   非法

匹配规则

情况一
  • 当 bindingKey 中没有 * 或 # 这两个特殊符号时,必须要求 routingKey 和 bindingKey 一模一样,才能算匹配成功!

实例理解

  • bindingKey = aaa.bbb.ccc
  • routingKey = aaa.bbb.ccc (匹配成功)
  • routingKey = aaa.bbb.ccd (匹配失败)

注意:

  • 情况一非常类似于 Direct 交换机的转发
  • 尤其是将 bindingKey 设置成和队列名字相同,此时就完全等价于 Direct 交换机了!

情况二
  • 当 bindingKey 中有特殊符号 * 时

实例理解 

  • bindingKey = aaa.*.ccc
  • routingKey = aaa.bbb.ccc (匹配成功)
  • routingKey = aaa.b.ccc (匹配成功)
  • routingKey = aaa.b.b.ccc(匹配失败)

情况三
  • 当 bindingKey 中有特殊符号 # 时

实例理解

  • bindingKey = aaa.#.ccc
  • routingKey = aaa.bbb.ccc (匹配成功)
  • routingKey = aaa.b.b.ccc (匹配成功)
  • routingKey = aaa.ccc(匹配成功)
  • routingKey = aaa.b.b.b(匹配失败)

问题:

  • 将交换机中每个队列的 bindingKey 设置成一个 # 时,会有啥效果呢?

回答:

  • 此时,该交换机中的 全部队列 都能匹配所有的 routingKey
  • 即该交换机就相当于 Fanout 交换机了!

注意点一:

  • Direct 交换机 和 Fanout 交换机,均属于 Topic 交换机的 特例

注意点二:

  • 上述规则都是 AMQP 协议所约定的!RabbitMQ 仅仅只是实现了该规则而已!

实现 Router 类

校验 bindingKey 和 routingKey

//    bindingKey 的构造规则:
//    1、数字,字母,下划线
//    2、使用 . 分割成若干部分
//    3、允许使用 * 和 # 作为通配符,但是通配符只能作为独立的分段public boolean checkBindingKey(String bindingKey) {if(bindingKey.length() == 0) {
//            空字符串也是合法情况,比如在使用 direct / fanout 交换机的时候,bindingKey 是用不上的
//            因为我们在使用 direct 交换机时,是直接将 routingKey 作为 消息队列的名字,直接根据名字来进行匹配
//            在使用 fanout 交换机时,无需匹配,直接将该消息转给交换机中绑定的所有消息队列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;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 的构造规则
//    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;}
//            该字符,不是上述任何一种合法情况,就直接返回 falsereturn false;}
//        把每个字符都检查过,没有遇到非法情况,此时直接返回 truereturn true;}

问题:

  • 观察下图,为啥 split 方法中的参数 "." 需要加两个反斜杠呢?

回答:

  • 首先 "." 在正则表达式中,是一个特殊的符号,此处是将 . 作为原始文本来进行匹配
  • 要想使用 . 的原始文本,就需要进行转义,即 在正则中使用 "\." 的方式来表示
  • 又因为在 Java 的字符串中,"\" 是一个特殊字符
  • 所以要想写入 "\." 这样的文本,又得在其前面加上一个反斜杠来进行转义,即 "\\."

消息匹配机制

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

Topic 交换机匹配规则

处理思路

  • 此处我们采用 双指针算法 进行匹配

  • 针对 bindingKey 的下标,判定当前下标指向部分的具体情况:
  1. 指向的是普通的字符串,此时要求和 routingKey 对应的下标指向的内容得完全一致!
  2. 指向的是 * ,此时无论  routingKey 这边指向的是啥,双方均同时下标前进
  3. 遇到了 # ,且 # 后面没有其他的内容了,直接返回 true,匹配成功!
  4. 遇到了 # ,但 # 后面还有其他的内容,拿着 # 后面的部分,去 routingKey 中查找,找到后面的部分,在 routingKey 中出现的位置,如果后面的部分,在 routingKey 中不存在,直接认为匹配失败,返回 false!如果后面的部分,在 routingKey 中存在,就将 routingKey 的箭头指向这个位置之后,然后继续往后匹配
  5. 两个箭头移动过程中,如果同时到达双方的末尾,则返回 true,如果一个箭头先到了末尾,另一个箭头还没到,则返回 false!

代码实现:

    private boolean routeTopic(Binding binding,Message message) {
//        先把这两个 key 进行切分String[] bindingTokens = binding.getBindingKey().split("\\.");String[] routingTokens = message.getRoutingKey().split("\\.");//        引入两个下标,指向上述两个数字,初始情况下都为 0int bindingIndex = 0;int routingIndex = 0;
//        此处使用 while 更合适,每次循环,下标不一定就是 +1,不适合使用 forwhile (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 的位置,返回该下标,没找到,就返回 -1routingIndex = 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;}

针对 Router 单元测试

  • 编写测试用例代码是十分重要的!
package com.example.demo;import com.example.demo.common.MqException;
import com.example.demo.mqserver.core.Binding;
import com.example.demo.mqserver.core.ExchangeType;
import com.example.demo.mqserver.core.Message;
import com.example.demo.mqserver.core.Router;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class RouterTests {private Router router = new Router();private Binding binding = null;private Message message = null;@BeforeEachpublic void setUp() {binding = new Binding();message = new Message();}@AfterEachprivate void tearDown() {binding = null;message = null;}//    【测试用例】
//    bindingKey               routingKey               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@Testpublic void test1() throws MqException {binding.setBindingKey("aaa");message.setRoutingKey("aaa");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test2() throws MqException {binding.setBindingKey("aaa.bbb");message.setRoutingKey("aaa.bbb");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test3() throws MqException {binding.setBindingKey("aaa.bbb");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test4() throws MqException {binding.setBindingKey("aaa.bbb");message.setRoutingKey("aaa.ccc");Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test5() throws MqException {binding.setBindingKey("aaa.bbb.ccc");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test6() throws MqException {binding.setBindingKey("aaa.*");message.setRoutingKey("aaa.bbb");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test7() throws MqException {binding.setBindingKey("aaa.*.bbb");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test8() throws MqException {binding.setBindingKey("*.aaa.bbb");message.setRoutingKey("aaa.bbb");Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test9() throws MqException {binding.setBindingKey("#");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test10() throws MqException {binding.setBindingKey("aaa.#");message.setRoutingKey("aaa.bbb");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test11() throws MqException {binding.setBindingKey("aaa.#");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test12() throws MqException {binding.setBindingKey("aaa.#.ccc");message.setRoutingKey("aaa.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test13() throws MqException {binding.setBindingKey("aaa.#.ccc");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test14() throws MqException {binding.setBindingKey("aaa.#.ccc");message.setRoutingKey("aaa.aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test15() throws MqException {binding.setBindingKey("#.ccc");message.setRoutingKey("ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}@Testpublic void test16() throws MqException {binding.setBindingKey("#.ccc");message.setRoutingKey("aaa.bbb.ccc");Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));}
}

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

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

相关文章

Spring Boot application.properties和application.yml文件的配置

在Spring Boot中&#xff0c;application.properties 和 application.yml 文件用于配置应用程序的各个方面&#xff0c;如服务器端口、数据库连接、日志级别等。这两个文件是Spring Boot的配置文件&#xff0c;位于 src/main/resources 目录下。 application.properties 示例 …

vue中动态表格中文校验,但是中文的参数无法获取

场景&#xff1a; 解决方案&#xff1a; 因为上述的正则表达式后&#xff0c;使用搜狗输入法没有问题&#xff0c;但采用微软自带输入法后&#xff0c;会存在输入数字及英文时为正常&#xff0c;切换为汉字后&#xff0c;会存在吞并当前光标前的字符。具体吞并个数和输入法中有…

Unity发布webgl获取浏览器的URL

Unity发布webgl获取浏览器的URL Unity发布webgl之后获取浏览器的url 在unity中创建文件夹Plugins&#xff0c;然后添加添加文件UnityGetBrowserURL.jslib var GetUrlFunc {//获取地址栏的URLStringReturnValueFunction: function () {var returnStr window.top.location.hre…

SpringBoot3整合Swagger3,访问出现404错误问题(未解决)

秉承着能用就用新的的理念&#xff0c;在JDK、SpringBoot、SpringCloud版本的兼容性下&#xff0c;选择了Java17、SpringBoot3.0.2整合Swagger3。 代码编译一切正常&#xff0c;Swagger的Bean也能加载&#xff0c;到了最后访问前端页面swagger-ui的时候出现404。 根据网上资料…

Oracle ADG相关介绍

文章目录 一、ADG原理1、ADG介绍2、ADG搭建流程 二、ADG相关参数三、增量修复 一、ADG原理 1、ADG介绍 Oracle ADG&#xff08;Advanced Data Guard&#xff09;是Oracle数据库的一项高可用和灾难恢复技术&#xff0c;它通过将数据保持在物理备库中来提供数据保护和容灾能力。…

StringBuffer StringBuilder

String 为什么StringBuilder是线程不安全的&#xff1f;StringBuffer是线程安全的&#xff1f; - Jacian - 博客园 (cnblogs.com) StringBuilder 线程安全的可变字符学序列 速度快 StringBuffer 线程不安全的可变字符序列 创建StringBuilder对象 new StringBuilder&…

c++面试一

1.#include使用 在C/C中&#xff0c;#include 预处理指令用于包含头文件&#xff0c;这些头文件通常包含了函数声明、宏定义以及其他的声明和定义。#include 指令后面跟着的文件名可以使用双引号 "" 或尖括号 <> 来指定&#xff0c;它们之间有一些区别。 双引…

Qt QWiget 实现简约美观的加载动画 第三季

&#x1f603; 第三季来啦 &#x1f603; 这是最终效果: 只有三个文件,可以直接编译运行 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <QVBoxLayout> #include <QGridLayout> int main(int argc, char *argv[]…

设计并实现一个并发安全的LRU(Least Recently Used,最近最少使用)缓存结构

文章目录 前言实战演示写在最后 前言 相信很多人都使用过LinkedHashMap&#xff0c;LinkedHashMap中的removeEldestEntry可以删除老旧的元素&#xff0c;我们可以以此来实现一个LRU缓存结构&#xff0c;并结合java中JUC包中的各种多线程锁机制来保证多线程安全。 以下是我遇见…

【前端素材】推荐优质后台管理系统Dashmin平台模板(附源码)

一、需求分析 后台管理系统在多个层次上提供了丰富的功能和细致的管理手段&#xff0c;帮助管理员轻松管理和控制系统的各个方面。其灵活性和可扩展性使得后台管理系统成为各种网站、应用程序和系统不可或缺的管理工具。 后台管理系统是一种具有多层次结构的软件系统&#xf…

邀请函 | 2024年数据技术嘉年华集结号已吹响,期待您参会!

龙腾四海内&#xff0c;风云际会时&#xff0c;2024年中国数据嘉年华如约而至。从起初小范围的网友聚会&#xff0c;到如今面向全国各地从业者、爱好者的年度集会&#xff0c;纵使岁月更迭&#xff0c;我们初心依旧。我们在各自最好的年华里共同见证了中国数据库行业的蓬勃发展…

Linux下的IO多路复用

文章目录 一. IO的概念和分类1. IO操作的原理&#xff1a;二. I/O多路复用使用场景和作用1. 问题&#xff1a; 一台网络服务器需要接收100台客户端的连接和数据通信&#xff0c;应该如何设计和实现&#xff1f;2. I/O多路复用机制&#xff1a; 三. Select poll epollselectpoll…

【前端素材】推荐优质后台管理系统Modernize平台模板(附源码)

一、需求分析 后台管理系统是一种用于管理和控制网站、应用程序或系统后台操作的软件工具&#xff0c;通常由授权用户&#xff08;如管理员、编辑人员等&#xff09;使用。它提供了一种用户友好的方式来管理网站或应用程序的内容、用户、数据等方面的操作&#xff0c;并且通常…

光学3D表面轮廓仪微纳米三维形貌一键测量

光学3D表面轮廓仪(白光干涉仪)利用白光干涉原理&#xff0c;以0.1nm分辨率精准捕捉物体的表面细节&#xff0c;实现三维显微成像测量&#xff0c;被广泛应用于材料学领域的研究和应用。 了解工作原理与技术 材料学领域中的光学3D表面轮廓仪&#xff0c;也被称为白光干涉仪&am…

SpringBoot -【SmartInitializingSingleton】基础使用及应用场景

SmartInitializingSingleton 在继续深入探讨 SmartInitializingSingleton接口之前&#xff0c;让我们先了解一下 Spring Framework 的基本概念和背景。Spring Framework 是一个开源的 JavaEE&#xff08;Java Enterprise Edition&#xff09;全栈&#xff08;full-stack&#x…

力扣 724. 寻找数组的中心下标

思路&#xff1a; 创建两个变量sum和sum1&#xff0c;sum代表左边元素的和&#xff0c;sum1代表右边元素的和 然后假设从数组下标0开始&#xff0c;一直到最后一个作为中心下标 如果sumsum1&#xff0c;返回此时的中心下标 如果所有下标循环完了&#xff0c;发现没有return…

Apipost 数据模型功能API数据重复利用起来

在Apipost数据模型中用户可以预先创建多个数据模型&#xff0c;并在API设计过程中重复利用这些模型来构建API 创建数据模型 在左侧导航点击「数据模型」-「新建数据模型」在右侧工作台配置数据模型参数 引入数据模型 在API设计预定义响应期望下点击引用数据模型&#xff0c;…

操作系统——处理机调度

文章目录 进程调度0.概念1.调度分类高级调度低级调度中级调度七状态模型调度对比 2.进程调度进程调度的时机进程调度的方式进程的切换方式调度器/调度程序闲逛进程 3. 调度算法的评价指标CPU利用率系统吞吐量周转时间等待时间响应时间 4. 调度算法先来先服务(FCFS)短作业优先(S…

【软件测试】--功能测试1

一、测试介绍 什么是软件&#xff1f; 控制计算机硬件工作的工具。 什么是软件测试&#xff1f; 使用技术手段验证软件是否满足需求 软件测试的目的&#xff1f; 减少软件缺陷&#xff0c;保证软件质量。 测试主流技能 1、功能测试 2、自动化测试 3、接口测试 4、性能测试 ​…

MySQL-事务,properties文件解析,连接池

1.事务机制管理 1.1 Transaction事务机制管理 默认情况下是执行一条sql语句就保存一次&#xff0c;那么比如我们需要三条数据同时成功或同时失败就需要开启事务机制了。开启事务机制后执行过程中发生问题就会回滚到操作之前&#xff0c;相当于没有执行操作。 1.2 事务的特征 事…