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

news2025/1/15 13:35:51

文章目录

  • 虚拟主机设计
    • 虚拟主机分析
    • 交换机和虚拟主机之间的从属关系
    • 核心 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.coloradmin.cn/o/1516049.html

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

相关文章

外包干了20天,技术退步明显......

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

v-model 粗略解析

v-model 粗略解析 v-model是什么&#xff1f; 双向数据绑定&#xff0c;可以从data流向页面&#xff0c;也可以从页面流向data通常用于表单收集&#xff0c;v-model 默认绑定 value 值书写形式&#xff1a; v-model:value"" 或 v-model v-model原理是什么&#xf…

HTML5+CSS3+JS小实例:全屏背景切换动画

实例:全屏背景切换动画 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-s…

【论文笔记合集】LSTNet之循环跳跃连接

本文作者&#xff1a; slience_me LSTNet 循环跳跃连接 文章仅作为个人笔记 论文链接 文章原文 LSTNet [25] introduces convolutional neural networks (CNNs) with recurrent-skip connections to capture the short-term and long-term temporal patterns. LSTNet [25]引入…

电脑闹钟软件,电脑上定时提醒的软件

我们生活在一个忙碌的时代&#xff0c;工作、学习、生活等各种事务时常让我们忙得不知所措。而在这样的情况下&#xff0c;一款电脑闹钟软件&#xff0c;电脑上定时提醒的软件就成为了我们不可或缺的工具之一。 电脑闹钟软件&#xff0c;电脑上定时提醒的软件&#xff0c;是一…

SpringBoot集成flyway

简介 Flyway 是一款开源的数据库版本管理工具&#xff0c;它更倾向于规约优于配置的方式。Flyway 可以独立于应用实现管理并跟踪数据库变更&#xff0c;支持数据库版本自动升级&#xff0c;并且有一套默认的规约&#xff0c;不需要复杂的配置&#xff0c;Migrations 可以写成 …

Leetcode 1514 概率最大的路径

文章目录 1. 题目描述2. 我的尝试 1. 题目描述 原题链接&#xff1a;Leetcode 1514 概率最大的路径 给你一个由 n 个节点&#xff08;下标从 0 开始&#xff09;组成的无向加权图&#xff0c;该图由一个描述边的列表组成&#xff0c;其中 edges[i] [a, b] 表示连接节点 a 和 b…

数据分析可视化神器---streamlit框架,各种图表绘制,布局以及生产综合案例剖析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

实践致知第9享:Word中标题编号无法正常编辑

一、背景需求 在编辑文档时&#xff0c;多级目录结构却无法正常编辑。 二、解决方案 1右键选择“项目符号和编号”查看是否上下文中的编号格式都保持一致&#xff0c;如下图所示。 2当调整到同一种样式之后&#xff0c;用格式刷刷一下需要编辑的标题&#xff0c;先刷成同级别…

GDPU 竞赛技能实践 天码行空3

1. 五星填数 &#x1f496; 源代码 public class Main {static int[] nums new int[11];static boolean[] used new boolean[13];static long ans 0;static{used[7] true;used[11] true;}public static void main(String[] args){dfs(1);System.out.println(ans / 10);//…

定制红酒:从需求收集到成品交付,服务流程的完整性

在云仓酒庄洒派&#xff0c;云仓酒庄洒派提供从需求收集到成品交付的完整定制红酒服务流程&#xff0c;以确保每一瓶定制红酒都能满足消费者的期望。 首先&#xff0c;云仓酒庄洒派会与消费者进行深入的沟通&#xff0c;了解他们的定制需求和期望。这一环节是服务流程的基础&a…

C++ 作业 24/3/13

1、设计一个Per类&#xff0c;类中包含私有成员:姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员:成绩、Per类对象p1&#xff0c;设计这两个类的构造函数、析构函数和拷贝构造函数。 #include <iostream>using namespace std;c…

一款前端开发工具Hbuilder

背景&#xff1a;最近日在接触前同事留下的一个VUE项目&#xff08;只有前端代码&#xff0c;后台服务压根没写真不知道以前是怎么糊弄过去的&#xff09;时&#xff0c;发现一款可以快速开发前端的软件&#xff1b;今日分享一下。 当我打开项目时发现&#xff0c;有个app.vue…

CSS中position的属性有哪些,区别是什么

position有以下属性值&#xff1a; 属性值概述absolute生成绝对定位的元素&#xff0c;相对于static定位以外的一个父元素进行定位。元素的位置通过left、top、right、bottom属性进行规定。relative生成相对定位的元素&#xff0c;相对于其原来的位置进行定位。元素的位置通过…

Devin内测注册全攻略:一文带你快速体验最新AI软件工程师技术 ️

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

AI生成图片在各行各业的影响与未来发展趋势

在当今数字化时代&#xff0c;人工智能技术已经在各行各业发挥着日益重要的作用。其中&#xff0c;AI生成图片技术在不同领域的应用正逐渐展现出其巨大潜力。从艺术创作到医学诊断&#xff0c;从设计制造到娱乐产业&#xff0c;AI生成图片正以其高效、创新的特性&#xff0c;深…

Linux symfonos

信息搜集 https://yutianqaq.github.io/ 赛博雨天 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.4p1 Debian 10deb9u6 (protocol 2.0) | ssh-hostkey: | 2048 ab:5b:45:a7:05:47:a5:04:45:ca:6f:18:bd:18:03:c2 (RSA) | 256 a0:5f:40:0a:0a:…

leetCode刷题 12. 整数转罗马数字

1. 思路 罗马数字的转换可以通过贪心算法来实现。我们可以按照罗马数字的规则&#xff0c;从大到小依次匹配并减去对应的值&#xff0c;直到 num 变为 0。 2. 解题方法 初始化一个 StringBuilder 用于存储转换后的罗马数字。枚举所有的罗马数字符号&#xff0c;按照从大到小…

活动预告:如何培养高质量应用型医学人才?

在大数据时代与“新医科”建设的背景下&#xff0c;掌握先进的医学数据处理技术成为了医学研究与应用的重要技能。 为了更好地培养社会所需要的高质量应用型医学人才&#xff0c;许多高校已经在广泛地开展面向医学生的医学数据分析教学工作。 在“课-训-赛”育人才系列活动的…

Clion配置并使用rsync

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、rsync是什么&#xff1f;二、安装1.Linux2.Windows 三、在Clion中配置四、在Clion中使用总结 前言 Clion这个工具和别的IDE不太一样&#xff0c;虽然都是J…