RabbitMQ 模拟实现【四】:虚拟主机设计

文章目录

  • 虚拟主机设计
    • 虚拟主机分析
    • 交换机和虚拟主机之间的从属关系
    • 核心 API
    • 发布消息
    • 订阅消息
    • 应答消息
    • 消费者管理类

虚拟主机设计

虚拟主机分析

类似于 MySQL 的 database,把交换机,队列,绑定,消息…进⾏逻辑上的隔离,⼀个服务器可以有多
个虚拟主机~,此处我们项⽬就设计了⼀个虚拟主机(VirtualHost)来提供 API 供上层调用

咱们这里采取的方案是,在客户提供的交换机等的身份标识(交换机名字),前加上虚拟机的名字. 即 客户要在虚拟机
VirtualHostA中创建交换机 exchangeC,咱们服务器存储的交换机名字是 VirtualHostAexchangeC.

交换机和虚拟主机之间的从属关系

  • ⽅案⼀:参考数据库设计,“⼀对多”⽅案,⽐如给交换机表,添加个属性,虚拟主机 id/name
  • ⽅案⼆:交换机的名字 = 虚拟主机名字 + 交换机的真实名字(按照⽅案⼆,也可以去区分不同的队列,进⼀步由于,绑定和队列和交换机都相关,直接就隔离开了,再进⼀步,消息和队列是强相关的,队列名区分开,消息⾃然区分开。)

核心 API

在这里插入图片描述

发布消息

发布消息API:

  • 其实就是生产者将消息发送给对应的交换机,交换机再根据不同的转发规则,转发给与之相绑定且符合规则的消息队列.
  • 绑定关系 Binding 中有一个 bindingKey 属性
  • 消息 Message 中 有一个 routingKey 属性

下面就来讲解一下三种交换机的转发规则已经这两个 Key 的不同含义.

  • 直接交换机 DIRECT 转发规则

    • 在直接交换机中,bindingKye是无意义的,routingKey是要转发到的队列的队列名.
    • 直接交换机的转发规则, 是无视 bindingKey的,即 直接交换机是否与这个队列绑定都没有关系,而直接将消息转发到 routingKey指定的队列名的队列中.
  • 扇出交换机 FANOUT 转发规则

    • 在扇出交换机中,bindingKye是绑定的要转发的队列,routingKey是无意义的.
    • 扇出交换机的转发规则,是将收到的消息转发到与之绑定的所有队列中.与bindingKye和routingKey是没有任何关系的.
  • 主题交换机 TOPIC 转发规则

    • 在主题交换机中,
    • bindingKey是创建绑定时,给绑定指定的特殊字符串(相当于一把锁),
    • routingKey是转发消息时,给消息指定的特殊字符串(相当于一把钥匙).
    • 主题交换机的转发规则,是将收到的消息的routingKey与绑定的所有队列中的 bindingKey 进行匹配,当且仅当匹配成功时,才将消息转发给该队列.

匹配规则 - AMQP 协议

  • routingKey规则
    由数字,字母,下划线组成,使用 . 将routingKey分成多个部分.
  • bindingKey规则
    由数字,字母,下划线组成,使用 . 将routingKey分成多个部分(支持两种特殊的符号作为通配符 * 与 # (和#必须是作为被 . 分割出来的单独部分如 aaa.bb就是非法的,* 可以匹配任何一个独立的部分,# 可以匹配0个或多个的独立部分)

相关代码实现

import com.example.demo.common.MqException;

// 使用这个类来实现交换机的转发规则
// 同时通过这个类来验证 bindingKey 是否合法
public class Router {

    public  boolean checkBindingKey(String bindingKey){
        if (bindingKey.length() == 0) {
            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;
        }
        // 检查 * 或者 # 是否是独立的部分.
        String[] words = bindingKey.split("\\.");
        for (String word : words) {
            // 检查 word 长度 > 1 并且包含了 * 或者 # , 就是非法的格式了.
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }
        // 约定一下, 通配符之间的相邻关系(人为约定)
        // 只有 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;
    }

    // 数字 + 字母 + 下划线
    // 使用.分割若干部分
    public boolean checkRoutingKey(String routingKey){
        if(routingKey.length()==0){
            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;
        }
        return true;
    }

    // 判定该消息是否可以转发给这个绑定对应的队列
    public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {
        if(exchangeType == ExchangeType.FANOUT){
            return true;
        } else if (exchangeType == ExchangeType.TOPIC){
            return routeTopic(binding,message);
        } else {
            throw new MqException("[Router] 交换机类型非法! exchange= " + exchangeType);
        }
    }

    // 约定匹配规则
    private 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++;
                continue;
            } else if (bindingTokens[bindingIndex].equals("#")) {
                // 如果遇到 #, 需要先看看有没有下一个位置.
                bindingIndex++;
                if (bindingIndex == bindingTokens.length) {
                    //  # 匹配成功
                    return true;
                }
                //  #  拿着后面的内容, 去 routingKey 中往后找, 找到对应的位置.
                // findNextMatch 查找 返回该下标. 没找到, 就返回 -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++;
            }
        }

        // 判定是否是双方同时到达末尾
        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;
    }
}

订阅消息

  • 新来的消息要转发给哪个消费者呢?
    咱们在这里采取轮询策略,即让消费者排队,依次将消息发送给消费者,当消费者收到消息后,则移动到队伍的最后等待下个消息.
    因此咱们要给核心类 Message类再增加几个属性和方法,来管理消费者

  • 自动发送消息至订阅者
    那么消费者要如何拿到消息呢?即如何将消息发送给消费者,咱们这里采取的是自动发送,即队列中来了新消息,就自动将新消息发送给订阅了这个队列的消费者.

咱们实现的方法是,使用一个阻塞队列,当生产者发布消息到交换机时,交换机转发消息到对应的队列后,就把队列名当作令牌添加到这个阻塞队列中,再配置一个扫描线程,去时刻扫描这个阻塞队列中是否有新的令牌了,有了新令牌,则根据令牌去对应的队列中,去把新消息安装轮询策略转发给消费者.

应答消息

应答消息共有两种模式.
自动应答:将消息发送给消费者就算应答了(不关心消费者收没收到,相当于没应答)
手动应答:需要消费者手动调用应答方法(确保消费者收到消息了)

消费者管理类

关于消费者,咱们并不打算持久化存储消费者的信息,即只在内存中存储消费者信息,如果服务器重启后,那么内存中的消费者信息也会清空,此时消费者就需要重新订阅消息.
在这里插入图片描述

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

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

相关文章

医学数据分析中缺失值的处理方法

医学数据分析中缺失值的处理方法 &#xff08;为了更好的展示&#xff0c;在和鲸社区使用代码进行展示&#xff09; 医学数据分析中&#xff0c;缺失值是不可避免的问题。缺失值的存在会影响数据的完整性和准确性&#xff0c;进而影响分析结果的可靠性。因此&#xff0c;在进…

php+vue+mysql公司员工薪酬工资管理系统

采用面向对象的思维方式&#xff0c;以符合实际的功能与性能要求&#xff0c;并进行了创新。为了提升小型企业工资管理的自动化和友善性的小型企业工资管理系统。 本文提出了一种基于面向对象的思想方法&#xff0c;以适应系统的实际功能与性能要求。为了使小型企业工资管理更具…

柚见第十期(后端队伍接口详细设计)

创建队伍 用户可以 创建 一个队伍&#xff0c;设置队伍的人数、队伍名称&#xff08;标题&#xff09;、描述、超时时间 P0 队长、剩余的人数 聊天&#xff1f; 公开 或 private 或加密 信息流中不展示已过期的队伍 请求参数是否为空&#xff1f;是否登录&#xff0c;未登录不…

决策树 | 分类树回归树:算法逻辑

目录 一. 决策树(Decision Tree)1. 决策树的构建1.1 信息熵(Entropy)1.1.1 信息量&信息熵 定义1.1.2 高信息熵&低信息熵 定义1.1.3 信息熵 公式 1.2 信息增益(Information Gain)1.2.1 信息增益的计算1.2.2 小节 2. 小节2.1 算法分类2.2 决策树算法分割选择2.3 决策树算…

Python应用数值方法:工程与科学实践指南

信息技术时代的挑战与机遇 我们正处在一个信息技术高速发展的时代&#xff0c;这是一个科技与创新蓬勃发展的时代。大数据与人工智能的崛起&#xff0c;正以前所未有的速度推动着传统技术的智能化变革。这种变革不仅带来了前所未有的机遇&#xff0c;也对科学和工程技术人员的…

什么时候要分库分表

对于一个日活用户在百万数量级的商城来说&#xff0c;每天产生的订单数量可能在百万级&#xff0c;特别在一些活动促销期间&#xff0c;甚至上千万。 假设我们基于单表来实现&#xff0c;每天产生上百万的数据量&#xff0c;不到一个月的时间就要承受上亿的数据&#xff0c;这…

水库大坝安全监测中需要注意的事项

随着经济和社会的发展&#xff0c;水资源的需求也在不断增加。因此&#xff0c;建设水库已成为保障水资源的主要方式之一。然而&#xff0c;随着水库规模的增大和工程的复杂性的增加&#xff0c;水库大坝的安全问题也日益引起重视。为此&#xff0c;需要对水库大坝进行安全监测…

2024年云服务器ECS价格表出炉——阿里云

2024年阿里云服务器租用费用&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核4G服务…

变量的本质和命名规则

变量的本质 内存:计算机中存储数据的地方&#xff0c;相当于一个空间变量本质:是程序在内存中申请的一块用来存放数据的小空间 变量命名规则与规范 规则: 不能用关键字 关键字:有特殊含义的字符&#xff0c;JavaScript 内置的一些英语词汇。例如:let、var、if、for等>只…

2024阿里技术官重磅推出“Java进阶必备宝典” 5大专题 6000字解析

5.JVM实战 CPU占用过高案例实战 内存占用过高案例实战 15种方式编写高效优雅Java程序实战 6.JVM底层技术 亿级流量高井发下GC预估与调优 JHSDB工具透视L ambda底层实现 JVM(HotSpot)核心源码解读 JVM核心模块(GC算法)手写实战 核心三&#xff1a;网络编程与高效IO 1.网络…

人形双臂机器人重大进展!顶刊公布业界首个双臂通用协同操作架构

图1&#xff1a;人居环境下的人形双臂机器人系统 通用人形机器人作为近年来机器人与AI交叉领域的研究热点和技术竞争高地&#xff0c;因其具备在非结构化人居环境中承担各种琐碎家务的潜力而得到广泛关注。人形双臂系统直接承载着人形机器人操作任务的执行能力&#xff0c;通用…

使用ai智能工具,让短视频超强变现。利用人工智能创作短视频

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 以下文章简单介绍如何利用人工智能来制作短视频&#xff0c;来实现资源变现。 一、…

ARM TrustZone技术解析:构建嵌入式系统的安全扩展基石

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-LOdvohfCEnd8eKyd {font-family:"trebuchet ms",verdana,arial,sans-serif;f…

阿里云服务器租用费用价格表(2024年新版报价)

2024阿里云服务器优惠活动政策整理&#xff0c;阿里云99计划ECS云服务器2核2G3M带宽99元一年、2核4G5M优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M服务器61元一年、2核4G4M带宽165元1年&#xff0c;云服务器4核16G10M带宽26元1个月、149元半年&#xff0c;云服务器8核…

C#制作软件时窗体的弹出与嵌入

文章目录 一、窗体的弹出二、窗体的嵌入 一、窗体的弹出 这里面我们以Windows窗体应用程序为例&#xff0c;这里面达到的效果如下&#xff1a; 点击指定按钮&#xff0c;弹出目标窗口。接下来我们看具体操作&#xff1a; 这是我们的主窗体&#xff1a; 接下来我们需要在这个…

表结构设计

三个范式&#xff1a; 一范式要求所有属性都是不可分的基本数据项&#xff1b;二范式解决部分依赖&#xff1b;三范式解决传递依赖。 真实的业务场景是工程实现&#xff0c;表结构设计做好以下几点就已经足够&#xff1a; 每张表一定要有一个主键&#xff08;方法有自增主键…

285K Star,一个让开发变得更简单的 GitHub 项目

Hi&#xff0c;骚年&#xff0c;我是大 G&#xff0c;公众号「GitHub 指北」会推荐 GitHub 上有趣有用的项目&#xff0c;一分钟 get 一个优秀的开源项目&#xff0c;挖掘开源的价值&#xff0c;欢迎关注。 导语 公共 API&#xff08;Application Programming Interface&…

【框架学习 | 第六篇】SpringBoot基础篇(快速入门、自动配置原理分析、配置文件、整合第三方技术、拦截器、文件上传/下载、访问静态资源)

文章目录 1.SpringBoot简介1.1原有Spring优缺点分析1.1.1Spring优点1.1.2Spring缺点 1.2SpringBoot概述1.2.1SpringBoot解决上述Spring的缺点1.2.2SpringBoot特点1.2.3SpringBoot核心功能 2.SpringBoot快速入门2.1代码实现2.1.1创建Maven工程2.1.2添加SpringBoot的起步依赖2.1.…

HTML CSS入门:从基础到实践

&#x1f310; HTML & CSS入门&#xff1a;从基础到实践 &#x1f3a8; &#x1f4d6; 引言 HTML和CSS是构建网页的基石。HTML&#xff08;超文本标记语言&#xff09;用于创建网页内容&#xff0c;而CSS&#xff08;层叠样式表&#xff09;则用于美化这些内容。无论你是…

【Python】成功解决NameError: name ‘cv2‘ is not defined

【Python】成功解决NameError: name ‘cv2’ is not defined &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您…