参考RabbitMQ实现一个消息队列

文章目录

  • 前言
  • 小小消息管家
    • 1.项目介绍
    • 2. 需求分析
      • 2.1 API
      • 2.2 消息应答
      • 2.3 网络通信协议设计
    • 3. 开发环境
    • 4. 项目结构介绍
      • 4.1 配置信息
    • 5. 项目演示

前言

消息队列的本质就是阻塞队列,它的最大用途就是用来实现生产者消费者模型,从而实现解耦合以及削峰填谷

在分布式系统中不再是单个服务器而是服务器“集群”,如果我们我们直接A服务器给B服务器发送请求,B服务器给A服务器返回响应,这样的话我们AB的耦合较大,如果A或者B服务器挂了,我们业务也就崩溃了。引入消息队列之后我们将请求和响应都通过消息队列这个中间人来传递,就降低了耦合度。

同样的,如果我们AB服务器直接进行通信,如果A服务器突然发送许多请求,我们B服务器也会收到巨多请求的影响,AB由于硬件资源限制可能都会崩溃。如果我们引入消息队列,消息队列可以将许多请求接收存储下来,B服务器依然可以按照原有节奏取请求,不会一下子接收大量请求。这也就是我们所说的削峰填谷,也是类似的,就算请求过少,我们的服务器依然可以从消息队列中取出挤压得请求。

在分布式系统中,跨主机之间使用生产者消费者模型就显得非常重要了。 我们通常把阻塞队列封装成⼀个独立的服务器程序,并且新增一些功能。这样的程序我们就称为 消息队列

所以此次就仿照市面上得RabbitMQ实现一个简易的消息队列。

小小消息管家

1.项目介绍

将消息队列分成服务器模块、客户端模块、公共模块来实现。

  1. 服务器模块通过虚拟主机实现交换机、队列、绑定、消息等相关操作的隔离。虚拟主机主要负责对上述内容的数据进行硬盘(数据库和文件)以及内存管理、消息的三种转发方式如Direct、Fanout、Topic、为提供客户端API的实现。
  2. 客户端模块:实现连接管理提供建立连接、信道管理通过信道实现TCP连接的复用。在信道管理中实现客户端调用的API。
  3. 公共模块:约定客户端服务器的通信协议、数据传输过程中的序列化以及反序列化。

在这里插入图片描述

2. 需求分析

由于是实现一个生产者消费者模型,在消息队列中我们要实现的逻辑如下:我们的生产者客户端向服务器发送消息,消费者客户端向服务器订阅消息,服务器负责消息的存储和转发。

在这里插入图片描述

概念解读:

  • 虚拟主机 (VirtualHost):是⼀个逻辑上的集合,⼀个 BrokerServer 上可以存在多个 VirtualHost。
  • 交换机 (Exchange):生产者把消息先发送到 BrokerServer 的 Exchange 上。 再根据不同的规则把消息转发给不同的 Queue。
  • 队列 (Queue):真正用来存储消息,每个消费者决定自己从哪个 Queue 上读取消息。
  • 绑定 (Binding):Exchange 和 Queue 之间的关联关系。Exchange 和 Queue 可以理解成 “多对多” 关
    系。
  • 消息 (Message): 传递的内容。

2.1 API

我们的服务器提供以下API是西安消息队列的基本功能。

  1. 创建队列 (queueDeclare)
  2. 销毁队列 (queueDelete)
  3. 创建交换机 (exchangeDeclare)
  4. 销毁交换机 (exchangeDelete)
  5. 创建绑定 (queueBind)
  6. 解除绑定 (queueUnbind)
  7. 发布消息 (basicPublish)
  8. 订阅消息 (basicConsume)
  9. 确认消息 (basicAck)

我们的客户端提供以下API供客户使用消息队列,为了复用TCP连接,我们提供了一个Channel逻辑通道。所以在客户端我们还需要提供Channel的创建和关闭:

  1. 创建 Connection
  2. 关闭 Connection
  3. 创建 Channel
  4. 关闭 Channel
  5. 创建队列 (queueDeclare)
  6. 销毁队列 (queueDelete)
  7. 创建交换机 (exchangeDeclare)
  8. 销毁交换机 (exchangeDelete)
  9. 创建绑定 (queueBind)
  10. 解除绑定 (queueUnbind)
  11. 发布消息 (basicPublish)
  12. 订阅消息 (basicConsume)
  13. 确认消息 (basicAck)

2.2 消息应答

被消费者消费的消息,需要进行应答来确定我们消费者正确消费了消息。我们设置两种应答模式:

  1. 自动应答:消费者只要消费了消息,就算应答完毕。Broker直接删除这个消息。
  2. 手动应答:消费者手动调用应答接口(确认消息),Broker收到应答请求后删除这个消息。

2.3 网络通信协议设计

我们使用TCP 协议,来作为通信的底层协议。在这个基础上自定义应用层协议,实现客户端对服务器提供的功能远程调用。所以我们在协议中约定type标记调用的功能,length为消息的长度,payload为消息的具体内容。

请求和响应的格式如下:
在这里插入图片描述

其中 type取值如下:
• 0x1 创建 channel
• 0x2 关闭 channel
• 0x3 创建 exchange
• 0x4 销毁 exchange
• 0x5 创建 queue
• 0x6 销毁 queue
• 0x7 创建 binding
• 0x8 销毁 binding
• 0x9 发送 message
• 0xa 订阅 message
• 0xb 返回 ack
• 0xc 服务器给客户端推送的消息(被订阅的消息)

其中 payload 部分,会根据不同的 type,存在不同的格式。对于请求来说, payload 表示这次方法调用的各种参数信息,我们定义对应的类实现。
对于响应,payload 表示这次方法调用的返回值。

3. 开发环境

数据库:SQLite
开发语言:Java
技术框架:SpringBoot、SpringMVC、Mybatis
管理工具:Maven
开发工具:Intellij IDEA 2020.1.4
操作系统:Windows10

4. 项目结构介绍

在这里插入图片描述

  • common 包中约定通信协议包括请求响应格式以及不同的payload对应的数据格式;创建自定义异常类;实现序列化反序列化;定义消费者以及处理消息调用的函数接口。
  • demo 创建了一个消费者和生产者用于测试项目。
  • mqclient 客户端模块,提供创建连接的工厂类;定义完整连接的内容;定义channel实现客户端api
  • mqserver.core 定义了交换机、队列、消息、交换机类型、转发规则、消费消息的逻辑。
  • mqserver.datacenter 定义了交换机、队列、消息、绑定的存储以及管理。
  • mqserver.mapper:实现对sqlite数据库的操作。

4.1 配置信息

由于我们对于数据库的存储只涉及一小部分,所以此处我们利用sqlite进行数据管理。

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.14</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>mq</artifactId><version>0.0.1-SNAPSHOT</version><name>mq</name><description>mq</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc --><dependency><groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId><version>3.41.0.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter-test</artifactId><version>2.3.1</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

application.yml: 配置数据库信息
在这里插入图片描述

5. 项目演示

创建一个生产者客户端向服务器发送一个消息:

package com.example.mq.demo;import com.example.mq.mqclient.Channel;
import com.example.mq.mqclient.Connection;
import com.example.mq.mqclient.ConnectionFactory;
import com.example.mq.mqserver.core.ExchangeType;import java.io.IOException;/*** 这个类用来表示一个生产者.*  通常这是一个单独的服务器程序.*/
public class DemoProducer {public static void main(String[] args) throws IOException, InterruptedException {System.out.println("启动生产者");//创建一个连接ConnectionFactory factory = new ConnectionFactory();factory.setHost("127.0.0.1");factory.setPort(9090);//得到逻辑链接,这个就类似于socketConnection connection = factory.newConnection();Channel channel = connection.createChannel();// 通过逻辑连接创建交换机和队列channel.exchangeDeclare("testExchange", ExchangeType.DIRECT, true, false, null);channel.queueDeclare("testQueue", true, false, false, null);// 创建一个消息并发送byte[] body = "hello 欢迎消费消息".getBytes();boolean ok = channel.basicPublish("testExchange", "testQueue", null, body);System.out.println("消息投递完成! ok=" + ok);Thread.sleep(500);channel.close();connection.close();}
}

创建一个消费者客户端,订阅消费消息

package com.example.mq.demo;import com.example.mq.common.Consumer;
import com.example.mq.common.MqException;
import com.example.mq.mqclient.Channel;
import com.example.mq.mqclient.Connection;
import com.example.mq.mqclient.ConnectionFactory;
import com.example.mq.mqserver.core.BasicProperties;
import com.example.mq.mqserver.core.ExchangeType;import java.io.IOException;/*** @author zq* @date 2023-08-05 19:29*//** 这个类表示一个消费者.* 通常这个类也应该是在一个独立的服务器中被执行*/
public class DemoConsumer {public static void main(String[] args) throws IOException, MqException, InterruptedException {System.out.println("启动消费者!");ConnectionFactory factory = new ConnectionFactory();factory.setHost("127.0.0.1");factory.setPort(9090);Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.exchangeDeclare("testExchange", ExchangeType.DIRECT, true, false, null);channel.queueDeclare("testQueue", true, false, false, null);//订阅消息,并定义如何消费消息channel.basicConsume("testQueue", true, new Consumer() {@Overridepublic void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) throws MqException, IOException {System.out.println("[消费数据] 开始!");System.out.println("consumerTag=" + consumerTag);System.out.println("basicProperties=" + basicProperties);String bodyString = new String(body, 0, body.length);System.out.println("body=" + bodyString);System.out.println("[消费数据] 结束!");}});// 通过这个循环模拟一直等待消费完生产者生产的所有消息while (true) {Thread.sleep(500);}}
}

实现结果如下 :

在这里插入图片描述
在这里插入图片描述

通过结果我们可以看出,我们消费者成功取出订阅的队列中的消息进行消费

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

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

相关文章

如何将视频转成gif图?视频怎么转gif高清图片?

在看电视或是短视频的时候&#xff0c;总能发现一些有趣的片段&#xff0c;当想把这些视频转gif图片发送给朋友的时候该怎么处理呢&#xff1f;其实可以试试专业的视频转gif工具&#xff0c;本文介绍一个视频在线转gif的方法&#xff0c;一起来了解一下吧。 打开首页&#xff…

重发布选路问题

一、思路 &#xff1b; 1.增加不优选路开销解决选路不佳问题 2.用增加开销的方式使R1 不将ASBR传的R7传给另一台ASBR解决R1、R2、R3、R4pingR7环回环路 二、操作 ------IP地址配置如图 1.解决环路 [r2] ip ip-prefix a permit 7.7.7.0 24 [r2]route-policy huawei per…

c++ boost circular_buffer

boost库中的 circular_buffer顾名思义是一个循环缓冲器&#xff0c;其 capcity是固定的当容量满了以后&#xff0c;插入一个元素时&#xff0c;会在容器的开头或结尾处删除一个元素。 circular_buffer为了效率考虑&#xff0c;使用了连续内存块保存元素 使用固定内存&#x…

The Sandbox 与 D.OASIS 联手打造 D.OASIS 城市

我们非常高兴地宣布与 D.OASIS 建立合作伙伴关系&#xff0c;共同打造无与伦比的娱乐体验&#xff1a;The Sandbox 中的 D.OASIS 城市&#xff01; 作为合作的一部分&#xff0c;The Sandbox 和D.OASIS将共同打造 D.OASIS 城市&#xff0c;一座充满无限可能的大都市&#xff0…

CSS基础介绍笔记1

官方文档 CSS指的是层叠样式&#xff08;Cascading Style Sheets&#xff09;地址&#xff1a;CSS 教程离线文档&#xff1a;放大放小&#xff1a;ctrl鼠标滚动为什么需要css&#xff1a;简化修改HTML元素的样式&#xff1b;将html页面的内容与样式分离提高web开发的工作效率&…

Grafana 曲线图报错“parse_exception: Encountered...”

问题现象 配置的Grafana图报错如下&#xff1a; 原因分析 点开报错&#xff0c;可以看到报错详细信息&#xff0c;是查询语句的语法出现了异常。 变量pool的取值为None 解决方案 需要修改变量pool的查询SQL&#xff0c;修改效果如下&#xff1a; 修改后&#x…

华为OD机试(含B卷)真题2023 算法分类版,58道20个算法分类,如果距离机考时间不多了,就看这个吧,稳稳的

目录 一、数据结构1、线性表2、优先队列3、滑动窗口4、二叉树5、并查集6、栈 二、算法1、基础算法2、字符串3、图4、动态规划5、数学 三、漫画算法2&#xff1a;小灰的算法进阶参与方式 很多小伙伴问我&#xff0c;华为OD机试算法题太多了&#xff0c;知识点繁杂&#xff0c;如…

机器学习中的工作流机制

机器学习中的工作流机制 在项目开发的时候&#xff0c;经常需要我们选择使用哪一种模型。同样的数据&#xff0c;可能决策树效果不错&#xff0c;朴素贝叶斯也不错&#xff0c;SVM也挺好。有没有一种方法能够让我们用一份数据&#xff0c;同时训练多个模型&#xff0c;并用某种…

Java源码-Context源码解析

您好&#xff0c;我们来一起了解一下Java源码中的Context源码解析。 Context是Android中的一个重要的概念&#xff0c;在Android开发中可以用来获取应用程序的各种信息&#xff0c;如Activity、Service、Application等等。在Android中&#xff0c;Context是一个抽象类&#xf…

Apache+Tomcat 整合

目录 方式一&#xff1a;JK 1、下载安装包 2、添加依赖 3、启动服务&#xff0c;检查端口是否监听 4、提供apxs命令 5、检查是否确实依赖 6、编译安装 7、重要配置文件 方式二&#xff1a;http_proxy 方式三&#xff1a;ajp_proxy 方式一&#xff1a;JK 1、下载安装…

【大数据】Flink 详解(二):核心篇 Ⅰ

Flink 详解&#xff08;二&#xff09;&#xff1a;核心篇 Ⅰ 14、Flink 的四大基石是什么&#xff1f; ​ Flink 的四大基石分别是&#xff1a; Checkpoint&#xff08;检查点&#xff09;State&#xff08;状态&#xff09;Time&#xff08;时间&#xff09;Window&#xff…

Java、Android 之 TCP / IP

TCP、IP是一系列协议组成的网络分层模型 客户端向服务端发送请求可能会走N条链路&#xff0c;这个过程叫路由 TCP传输 一般在1--1024端口 必须连接以后才能传输数据 UDP协议通常只是发送数据 TCP连接 TCP需要建立连接才能通信&#xff0c;建立连接需要端口&#xff0c;Sock…

20.4 HTML 表单

1. form表单 <form>标签: 用于创建一个表单, 通过表单, 用户可以向网站提交数据. 表单可以包含文本输入字段, 复选框, 单选按钮, 下拉列表, 提交按钮等等. 当用户提交表单时, 表单数据会发送到服务器进行处理.action属性: 应指向一个能够处理表单数据的服务器端脚本或UR…

Zabbix监控系统

目录 一、zabbix简介 1.1 zabbix 是什么&#xff1f; 1.2 zabbix 监控原理 二、安装zabbix 5.0 2.1 部署 zabbix 服务端 2.2 部署 zabbix 客户端 2.3 自定义监控内容 作为一个云计算行业从业人员&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff…

一篇文章看懂Apipost Mock功能怎么用

在接口开发过程中&#xff0c;Mock功能可以帮助开发者快速测试和验证接口的正确性和稳定性&#xff0c;以便快速迭代和修复问题。Apipost推出智能Mock功能&#xff0c;可以在智能期望中填写一些触发条件&#xff0c;开启后&#xff0c;Apipost会根据已设置的触发条件&#xff0…

口-肠-脑轴与精神健康的关系

谷禾健康 在个体中&#xff0c;每个微生物栖息地都表现出独特的微生物种群模式。迄今为止&#xff0c;关于微生物组相关疾病的研究主要集中在器官特异性微生物组上。然而&#xff0c;器官间的微生物网络正逐渐成为生理功能和病理过程中的重要调节因子和治疗机会。 在正常情况下…

mybatis打印sql语句出现多余的limit关键字

1、事情起因 在项目中使用了PageHelper分页插件&#xff0c;由于需求特殊&#xff0c;需要自定义分页&#xff0c;代码编写完成后&#xff0c;事故出现了。 前端传参: {pageNum: 1,pageSize: 10, }已知表中数据10条&#xff0c;但是每次分页查询只有10条数据&#xff0c;排查…

Mac 创建和删除 Automator 工作流程,设置 Terminal 快捷键

1. 创建 Automator 流程 本文以创建一个快捷键启动 Terminal 的自动操作为示例。 点击打开 自动操作&#xff1b; 点击 新建文稿 点击 快速操作 选择 运行 AppleScript 填入以下内容 保存名为 “Open Terminal” 打开 设置 > 键盘&#xff0c;选择 键盘快捷键 以此选择 服…

二叉树的性质、前中后序遍历【详细】

1. 树概念2.二叉树的概念1.2二叉树的性质 3.二叉树遍历3.2前序遍历3.2 中序遍历3.3 后序遍历 1. 树概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合&#xff0c;有二叉树&#xff0c;N叉树等等。 子树…

JVM面试突击班2

JVM面试突击班2 对象被判定为不可达对象之后就“死”了吗 对象的生命周期 创建阶段 &#xff08;1&#xff09;为对象分配存储空间 &#xff08;2&#xff09;开始构造对象 &#xff08;3&#xff09;从超类到子类对static成员进行初始化 &#xff08;4&#xff09;超类成…