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;}// 该字符, 不是上述任何一种合法情况, 就直接返回 falsereturn 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 查找 返回该下标. 没找到, 就返回 -1routingIndex = 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.hqwc.cn/news/537865.html

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

相关文章

如何实现Git Push之后自动部署到服务器?

在平时个人开发的过程中是不是有这样的烦恼&#xff1a; 项目开发完成&#xff0c;Push之后 登录服务器&#xff0c;手动git pull&#xff0c;然后运行部署命令 这真的很烦诶&#xff01; 那么能不能Git push之后&#xff0c;远端服务器自动 Git pull 然后运行部署命令呢&a…

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

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

还是了解下吧,大语言模型调研汇总

大语言模型调研汇总 一. Basic Language ModelT5GPT-3LaMDAJurassic-1MT-NLGGopherChinchillaPaLMU-PaLMOPTLLaMABLOOMGLM-130BERNIE 3.0 Titan 二. Instruction-Finetuned Language ModelT0FLANFlan-LMBLOOMZ & mT0GPT-3.5ChatGPTGPT-4AlpacaChatGLMERNIE BotBard 自从Cha…

IO Watch:用 Arduino UNO 制造的可编程手表

MAKER:mblaz/译:趣无尽 Cherry(转载请注明出处) 关于手表的项目,之前我们已经介绍过一款《Arduino + 3D 打印 DIY 电子手表》。本期的项目同样的一款基于 Arduino UNO 的可编程的手表,相比之下制造门槛更高一些。同时它更成熟、实用,外形也很有设计感,非常的漂亮! 这…

探索谷歌的秘密花园:Google文件系统GFS之旅(Google File System)

文章目录 &#x1f3d8;️GFS系统架构GFS系统节点类型GFS实现机制 &#x1f34e;GFS特点采用中心服务器模式不缓存数据在用户态下实现只提供专用接口 容错机制⚒️Master容错机制&#x1f504; Chunk Server容错 &#x1f6e0; 系统管理技术 &#x1f3d8;️GFS系统架构 大型分…

IDEA开启Run Dashboard

1、Run Dashboard是什么&#xff0c;为什么要使用 Run Dashboard 是 IntelliJ IDEA 中的一个工具窗口&#xff0c;用于管理和监视项目中正在运行的应用程序和配置。它提供了一种集中管理运行和调试过程的方式&#xff0c;可以让开发人员更方便地查看和控制正在运行的应用程序。…

kubernetes-污点和容忍

kubernetes-污点和容忍 kubernetes-污点和容忍1、什么是污点(Taint)2、什么是容忍度(Toleration)3、为什么master节点上不跑业务pod4、这种打污点让我想到了通过nodename可以指定node节点5、怎么打污点5.1、基本用法5.2、查了一下chat5.3、effect污点效果的三种状态5.4、打标签…

SQLite—免费开源数据库系列文章目录

SQLite系列相关文章较多特开本文为了便于读者阅读特写了本索引和目录之用本文将不断更新中有需要的读者可以收藏本文便于导航到各个专题( 持续更新中......)。收藏一篇等于收藏一个系列文章 简介类&#xff1a; SQLite——世界上部署最广泛的免费开源数据库&#xff08;简介&…

一站式数据采集物联网平台:智能化解决方案,让数据管理更高效、更安全

JVS物联网平台的定位 JVS是企业信息化的“一站式解决方案”&#xff0c;其中包括了基础的数字化底座、各种企业级能力、企业内常见的应用&#xff0c;如下图所示&#xff1a; 整体平台能力层有三大基础能力&#xff1a; 低代码用于业务的定义;数据分析套件用于数据的自助式分…

渗透某巨型企业某个系统的奇葩姿势

前言 这个月都在做一个巨型合作企业的渗透测试&#xff0c;这个系统本无方式getshell&#xff0c;得亏我心够细&#xff0c;想的多&#xff0c;姿势够骚。 文中重码&#xff0c;且漏洞已修复。 过程 找到getshell点 目标系统功能很少&#xff0c;基本上就是一个展示页面&a…

C# EPPlus导出dataset----Excel2绘制图像

一、生成折线图方法 /// <summary> ///生成折线图 /// </summary> /// <param name="worksheet">sheet页数据 </param> /// <param name="colcount">总列数</param> /// &l…

如何通过libusb直接向zebra打印机发送zpl,跨平台win/linux

环境&#xff1a;windows & linux & Zebra打印机gt820 windows: 之前安装了Zebra打印机官方驱动&#xff0c;所以先卸载掉驱动。再安装Zadig&#xff0c;用Zadig工具来安装WinUSB驱动。 zadig下载&#xff1a;Zadig - USB driver installation made easy 记住这两个数…