目录
[二、核心 API](#二、核心 API)
[2.1 Broker](#2.1 Broker)
[2.2 交换机类型(Exchange Type)](#2.2 交换机类型(Exchange Type))
[2.3 持久化](#2.3 持久化)
[2.4 网络通信](#2.4 网络通信)
[2.5 客户端操作 API](#2.5 客户端操作 API)
[2.6 Connection 和 Channel 的关系](#2.6 Connection 和 Channel 的关系)
[4.1 数据库封装信息测试](#4.1 数据库封装信息测试)
[4.1.0 前期准备](#4.1.0 前期准备)
[4.1.1 初始化表断言测试](#4.1.1 初始化表断言测试)
[4.1.2 插入交换机断言测试](#4.1.2 插入交换机断言测试)
[4.1.3 删除交换机断言测试](#4.1.3 删除交换机断言测试)
[4.1.4 创造绑定断言测试](#4.1.4 创造绑定断言测试)
[4.1.5 删除绑定断言测试](#4.1.5 删除绑定断言测试)
[4.2 文件硬盘管理测试](#4.2 文件硬盘管理测试)
[4.2.0 前期准备](#4.2.0 前期准备)
[4.2.1 创建文件断言测试](#4.2.1 创建文件断言测试)
[4.2.2 读取文件数量断言测试](#4.2.2 读取文件数量断言测试)
[4.2.3 发送消息断言测试](#4.2.3 发送消息断言测试)
[4.2.4 获取文件所有数据断言测试](#4.2.4 获取文件所有数据断言测试)
[4.2.5 删除文件银盘消息断言测试](#4.2.5 删除文件银盘消息断言测试)
[4.2.6 文件垃圾回收断言测试](#4.2.6 文件垃圾回收断言测试)
[4.3 内存管理信息测试](#4.3 内存管理信息测试)
[4.3.0 前期准备](#4.3.0 前期准备)
[4.3.1 交换机创建删除断言测试](#4.3.1 交换机创建删除断言测试)
[4.3.2 队列创建删除断言测试](#4.3.2 队列创建删除断言测试)
[4.3.3 绑定创建删除断言测试](#4.3.3 绑定创建删除断言测试)
[4.3.4 消息创建删除断言测试](#4.3.4 消息创建删除断言测试)
[4.3.5 发送消息断言测试](#4.3.5 发送消息断言测试)
[4.3.6 未应答消息断言测试](#4.3.6 未应答消息断言测试)
[4.3.7 重启断言测试](#4.3.7 重启断言测试)
[4.4 转发规则断言测试](#4.4 转发规则断言测试)
[4.5 虚拟主机断言测试](#4.5 虚拟主机断言测试)
[4.5.0 前期准备](#4.5.0 前期准备)
[4.5.1 发送消息断言测试](#4.5.1 发送消息断言测试)
[4.5.2 消费者订阅消息先后顺序断言测试](#4.5.2 消费者订阅消息先后顺序断言测试)
[4.5.3 FANOUT 交换机订阅消费断言测试](#4.5.3 FANOUT 交换机订阅消费断言测试)
[4.5.4 TOPIC 交换机订阅消费断言测试](#4.5.4 TOPIC 交换机订阅消费断言测试)
[4.5.5 手动应答消息断言测试](#4.5.5 手动应答消息断言测试)
[4.5.6 交换机队列绑定创建上面的已经包括了](#4.5.6 交换机队列绑定创建上面的已经包括了)
[4.6 客户端断言测试](#4.6 客户端断言测试)
[4.6.0 前期准备](#4.6.0 前期准备)
[4.6.1 创建连接和信道断言测试](#4.6.1 创建连接和信道断言测试)
[4.6.2 交换机请求和删除断言测试](#4.6.2 交换机请求和删除断言测试)
[4.6.3 队列请求和删除断言测试](#4.6.3 队列请求和删除断言测试)
[4.6.4 绑定请求和删除断言测试](#4.6.4 绑定请求和删除断言测试)
[4.6.5 消息发送与订阅断言测试](#4.6.5 消息发送与订阅断言测试)
[4.7 创建服务器和客户端模拟测试](#4.7 创建服务器和客户端模拟测试)
[4.7.1 创建消费者类](#4.7.1 创建消费者类)
[4.7.2 创建生产者类](#4.7.2 创建生产者类)
[4.7.3 打开服务器, 并运行这两个类](#4.7.3 打开服务器, 并运行这两个类)
一、项目背景
在我学习到阻塞队列(BlockingQueue) , 我了解到, 阻塞队列最大的用途, 就是⽤来实现**⽣产者消费者模型**
⽣产者消费者模型是后端开发的常用编程方式, 存在一些优点
-
解耦合
-
削峰填谷
在实际的后端开发中,尤其是分布式系统里,跨主机之间使用生产者消费者模型,也是非常普遍的需求。
因此,我们通常会把阻塞队列,封装成一个独立的服务器程序,并且赋予其更丰富的功能.
这样的程序我们就称为消息队列(MessageQueue,MQ)
所以来仿照一下 RabbitMQ, 实现一个简单的队列
源码: https://github.com/QiLuNuo928/mq_/tree/main/src/main
N 个 生产者, N 个消费者:

二、核心 API
2.1 Broker
对于 Broker 来说,要实现以下核心 API。通过这些 API 来实现消息队列的基本功能。
-
创建队列 (queueDeclare)
-
销毁队列 (queueDelete)
-
创建交换机 (exchangeDeclare)
-
销毁交换机 (exchangeDelete)
-
创建绑定 (queueBind)
-
解除绑定 (queueUnbind)
-
发布消息 (basicPublish)
-
订阅消息 (basicConsume)
-
确认消息 (basicAck)
另一方面,Producer 和 Consumer 则通过网络的方式,远程调用这些 API,实现生产者消费者模型。
2.2 交换机类型**(Exchange Type)**
对于 RabbitMQ 来说,主要支持四种交换机类型:
-
Direct
-
Fanout
-
Topic
-
Header
其中 Header 这种方式比较复杂,比较少见。常用的是前三种交换机类型。哈们此处也主要实现这三种。
2.3 持久化
保证重启内容不丢失
2.4 网络通信
-
生产者和消费者都是客户端程序,Broker 作为服务器,通过网络进行通信。
-
在网络通信过程中,客户端需要提供对应的 API 来实现对服务器的操作

2.5 客户端操作 API
-
创建 Connection
-
关闭 Connection
-
创建 Channel
-
关闭 Channel
-
创建队列 (queueDeclare)
-
销毁队列 (queueDelete)
-
创建交换机 (exchangeDeclare)
-
销毁交换机 (exchangeDelete)
-
创建绑定 (queueBind)
-
解除绑定 (queueUnbind)
-
发布消息 (basicPublish)
-
订阅消息 (basicConsume)
-
确认消息 (basicAck)
2.6 Connection 和 Channel 的关系
-
Connection 对应一个 TCP 连接。
-
Channel 是 Connection 中的逻辑通道。
-
一个 Connection 中可以包含多个 Channel。
三、模块划分

四、功能测试
4.1 数据库封装信息测试
4.1.0 前期准备
我们将交换机队列绑定存储在 sqlite 中, 主要测试增删改查以及初始化的数据库建表操作
java
@BeforeEach
public void setUp(){
//由于在init中, 需要 context 对象拿到 metaMapper 实例
MqApplication.context = SpringApplication.run(MqApplication.class);
dataBaseManger.init();
}
@AfterEach
public void tearDown(){
//此处不能直接删除, 需要关闭上面的context对象
//此处context对象持有, MetaMapper的实例化对象, 而MetaMapper又打开了数据库链接
MqApplication.context.close();
dataBaseManger.deleteDB();
}
通过 setUp 和 tearDown 来每次测试自动删除表和断开数据库连接
4.1.1 初始化表断言测试
java
@Test
public void testInitTable(){
List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
List<MSGQueue> msgQueueList = dataBaseManger.selectAllMSGQueues();
List<Binding> bindingList = dataBaseManger.selectAllBindings();
//使用断言, 这两个参数的顺序, 无所谓
//但是第一个形参叫做 期望的 expected, 第一个形参叫做 实际的 actual
Assertions.assertEquals(1,exchangeList.size());
Assertions.assertEquals("",exchangeList.get(0).getName());
Assertions.assertEquals(ExchangeType.DIRECT,exchangeList.get(0).getType());
Assertions.assertEquals(0,msgQueueList.size());
Assertions.assertEquals(0, bindingList.size());
}
4.1.2 插入交换机断言测试
java
@Test
public void insertExchangeTest(){
//构造一个exchange 插入查询观察是否相同
Exchange exchange = createExchangeTest("testExchange");
dataBaseManger.insertExchange(exchange);
List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
Assertions.assertEquals(2,exchangeList.size());
Assertions.assertEquals("testExchange",exchangeList.get(1).getName());
Assertions.assertEquals(ExchangeType.DIRECT,exchangeList.get(1).getType());
Assertions.assertEquals(true,exchangeList.get(1).isDurable());
Assertions.assertEquals(false,exchangeList.get(1).isAutoDelete());
Assertions.assertEquals(1,exchangeList.get(1).getArguments("aaa"));
Assertions.assertEquals(2,exchangeList.get(1).getArguments("bbb"));
}
4.1.3 删除交换机断言测试
java
@Test
public void deleteExchangeTest(){
//构造一个exchange 插入查询观察是否相同
Exchange exchange = createExchangeTest("testExchange");
dataBaseManger.insertExchange(exchange);
List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
Assertions.assertEquals(2,exchangeList.size());
Assertions.assertEquals("testExchange",exchangeList.get(1).getName());
//进行删除操作
dataBaseManger.deleteExchange("testExchange");
exchangeList = dataBaseManger.selectAllExchanges();
Assertions.assertEquals(1,exchangeList.size());
Assertions.assertEquals("",exchangeList.get(0).getName());
}
4.1.4 创造绑定断言测试
java
@Test
public void insertBindingTest(){
Binding bindIng = createBindingTest("testExchange","testQueue");
dataBaseManger.insertBinding(bindIng);
List<Binding> bindingList = dataBaseManger.selectAllBindings();
Assertions.assertEquals(1, bindingList.size());
Assertions.assertEquals("testExchange", bindingList.get(0).getExchangeName());
Assertions.assertEquals("testQueue", bindingList.get(0).getQueueName());
Assertions.assertEquals("testBindingKey", bindingList.get(0).getBindingKey());
}
4.1.5 删除绑定断言测试
java
@Test
public void deleteBindingTest(){
Binding bindIng = createBindingTest("testExchange","testQueue");
dataBaseManger.insertBinding(bindIng);
List<Binding> bindingList = dataBaseManger.selectAllBindings();
Assertions.assertEquals(1, bindingList.size());
Assertions.assertEquals("testExchange", bindingList.get(0).getExchangeName());
Assertions.assertEquals("testQueue", bindingList.get(0).getQueueName());
Assertions.assertEquals("testBindingKey", bindingList.get(0).getBindingKey());
//删除
Binding toDeleteBinding = createBindingTest("testExchange","testQueue");
dataBaseManger.deleteBinding(toDeleteBinding);
bindingList = dataBaseManger.selectAllBindings();
Assertions.assertEquals(0, bindingList.size());
}
其他的不举例子源码中都包含

一次测试全部成功
4.2 文件硬盘管理测试
4.2.0 前期准备
同样对前后测试一个用例进行处理, 防止影响数据库的数据
java
@BeforeEach
public void setUp() throws IOException {
// 准备阶段, 创建出两个队列, 以备后用
messageFileManager.createQueueFiles(queueName1);
messageFileManager.createQueueFiles(queueName2);
}
//收尾
@AfterEach
public void tearDown() throws IOException {
//收尾会把创建的两个队列销毁掉
messageFileManager.destroyQueueFiles(queueName1);
messageFileManager.destroyQueueFiles(queueName2);
}
4.2.1 创建文件断言测试
java
@Test
public void createFilesTest(){
//创建文件已经在开始执行过了, 验证文件是否存在
File queueDataFile1 = new File("./data/" + queueName1 + "/queue_data.txt");
Assertions.assertEquals(true,queueDataFile1.isFile());
File queueStatFile1 = new File("./data/" + queueName1 + "/queue_stat.txt");
Assertions.assertEquals(true,queueStatFile1.isFile());
File queueDataFile2 = new File("./data/" + queueName2 + "/queue_data.txt");
Assertions.assertEquals(true,queueDataFile2.isFile());
File queueStatFile2 = new File("./data/" + queueName2 + "/queue_stat.txt");
Assertions.assertEquals(true,queueStatFile2.isFile());
}
4.2.2 读取文件数量断言测试
java
@Test
public void ReadWriteStatTest(){
MessageFileManager.Stat stat = new MessageFileManager.Stat();
stat.totalCount = 100;
stat.validCount = 50;
// 此处需要使用反射的方式来调用 writeStat 和 readStat
// Java原生的反射比较难用, 我们使用 spring 封装好的反射工具来使用
ReflectionTestUtils.invokeMethod(messageFileManager,"writeStat",queueName1,stat);
// 写入完毕之后, 再调用一下读取, 验证一下读取结果与写入结果是否一致
MessageFileManager.Stat newStat = ReflectionTestUtils.invokeMethod(messageFileManager,"readStat",queueName1);
Assertions.assertEquals(100,newStat.totalCount);
Assertions.assertEquals(50,newStat.validCount);
}
4.2.3 发送消息断言测试
java
@Test
public void sentMessageTest() throws IOException, MqException, ClassNotFoundException {
// 构造出消息, 并且构造出队列
Message message = createTestMessage("testMessage");
// 此处创建的 queue 对象的 name 不能随便写, 因为需要保证该队列的文件和目录都要存在
//我们开始只准备了两个队列的文件和目录, 所以只能用那两个先来测试
MSGQueue queue = createTestQueue(queueName1);
// 调用发送消息的方法
messageFileManager.sendMessage(queue,message);
//检查一下 stat 文件
MessageFileManager.Stat stat = ReflectionTestUtils.invokeMethod(messageFileManager,"readStat",queueName1);
Assertions.assertEquals(1,stat.totalCount);
Assertions.assertEquals(1,stat.validCount);
//检查一下 data 文件
LinkedList<Message> messages = messageFileManager.loadAllMessageFromQueue(queueName1);
Assertions.assertEquals(1,messages.size());
Message curMessage = messages.get(0);
Assertions.assertEquals(message.getMessageId(),curMessage.getMessageId());
Assertions.assertEquals(message.getRoutingKey(),curMessage.getRoutingKey());
Assertions.assertEquals(message.getDeliverMode(),curMessage.getDeliverMode());
//比较两个字节数组是否相同不可以使用 assertEquals
Assertions.assertArrayEquals(message.getBody(),curMessage.getBody());
System.out.println("message: " + curMessage);
}
4.2.4 获取文件所有数据断言测试
java
@Test
public void loadAllMessageFromQueueTest() throws IOException, MqException, ClassNotFoundException {
// 往队列插入 100 条消息, 然后验证这 100 条消息从文件中读取后, 看看是否一致
MSGQueue queue = createTestQueue(queueName1);
LinkedList<Message> expectedMessages = new LinkedList<>();
for (int i = 0; i < 100; i++) {
Message message = createTestMessage("testMessage" + i);
messageFileManager.sendMessage(queue,message);
expectedMessages.add(message);
}
// 读取所有的消息
LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
Assertions.assertEquals(expectedMessages.size(),actualMessages.size());
for (int i = 0; i < actualMessages.size(); i++) {
Message expectedMessage = expectedMessages.get(i);
Message actualMessage = actualMessages.get(i);
System.out.println("[" + i + "] actualMessage = " + actualMessage);
Assertions.assertEquals(expectedMessage.getMessageId(),actualMessage.getMessageId());
Assertions.assertEquals(expectedMessage.getRoutingKey(),actualMessage.getRoutingKey());
Assertions.assertEquals(expectedMessage.getDeliverMode(),actualMessage.getDeliverMode());
Assertions.assertArrayEquals(expectedMessage.getBody(),actualMessage.getBody());
Assertions.assertEquals(0x1,actualMessage.getIsValid());
}
}
4.2.5 删除文件银盘消息断言测试
java
@Test
public void testDeleteMessage() throws IOException, MqException, ClassNotFoundException {
MSGQueue queue = createTestQueue(queueName1);
LinkedList<Message> expectedMessages = new LinkedList<>();
for (int i = 0; i < 10; i++) {
Message message = createTestMessage("testMessage" + i);
messageFileManager.sendMessage(queue,message);
expectedMessages.add(message);
}
// 删除其中 3 个消息
messageFileManager.deleteMessage(queue,expectedMessages.get(9));
messageFileManager.deleteMessage(queue,expectedMessages.get(8));
messageFileManager.deleteMessage(queue,expectedMessages.get(7));
// 对比这里的内容是否正确
LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
Assertions.assertEquals(7,actualMessages.size());
for (int i = 0; i < 7; i++) {
Message expectedMessage = expectedMessages.get(i);
Message actualMessage = actualMessages.get(i);
System.out.println("[" + i + "] actualMessage = " + actualMessage);
Assertions.assertEquals(expectedMessage.getMessageId(),actualMessage.getMessageId());
Assertions.assertEquals(expectedMessage.getRoutingKey(),actualMessage.getRoutingKey());
Assertions.assertEquals(expectedMessage.getDeliverMode(),actualMessage.getDeliverMode());
Assertions.assertArrayEquals(expectedMessage.getBody(),actualMessage.getBody());
Assertions.assertEquals(0x1,actualMessage.getIsValid());
}
}
4.2.6 文件垃圾回收断言测试
java
@Test
public void gcTest() throws IOException, MqException, ClassNotFoundException {
// 先往队列写 100 个消息
// 再把 100 个消息的一半都给删除掉
// 手动调用 gc 方法, 检测新文件的大小是否比之前缩小了
MSGQueue queue = createTestQueue(queueName1);
LinkedList<Message> expectedMessages = new LinkedList<>();
for (int i = 0; i < 100; i++) {
Message message = createTestMessage("testMessage" + i);
messageFileManager.sendMessage(queue,message);
expectedMessages.add(message);
}
// 获取 gc 前的文件大小
File beforeGcFile = new File("./data/" + queueName1 + "/queue_data.txt");
long beforeGcLength = beforeGcFile.length();
System.out.println("beforeGcLength 文件大小 = " + beforeGcLength);
// 删除偶数下标的信息
for (int i = 0; i < 100; i += 2) {
messageFileManager.deleteMessage(queue,expectedMessages.get(i));
}
//手动调用 gc
messageFileManager.gc(queue);
//重新读取文件, 验证新的文件的内容是否和之前的内容匹配
LinkedList<Message> actualMessages = messageFileManager.loadAllMessageFromQueue(queueName1);
for (int i = 0; i < actualMessages.size(); i++) {
// 把之前偶数下表的都删了, 只剩下奇数下标的了
Message expectedMessage = expectedMessages.get(2 * i + 1);
Message actualMessage = actualMessages.get(i);
Assertions.assertEquals(expectedMessage.getMessageId(),actualMessage.getMessageId());
Assertions.assertEquals(expectedMessage.getRoutingKey(),actualMessage.getRoutingKey());
Assertions.assertEquals(expectedMessage.getDeliverMode(),actualMessage.getDeliverMode());
Assertions.assertArrayEquals(expectedMessage.getBody(),actualMessage.getBody());
Assertions.assertEquals(0x1,actualMessage.getIsValid());
}
// 获取新文件的大小
File afterGcFile = new File("./data/" + queueName1 + "/queue_data.txt");
long afterGcLength = afterGcFile.length();
System.out.println("afterGcLength 文件大小 = " + afterGcLength);
Assertions.assertTrue(beforeGcLength > afterGcLength);
}
一次性测试全部成功

4.3 内存管理信息测试
4.3.0 前期准备
一样先做好前置条件
java
@BeforeEach
public void setUp(){
memoryDataCenter = new MemoryDataCenter();
}
@AfterEach
public void tearDown(){
memoryDataCenter = null;
}
4.3.1 交换机创建删除断言测试
java
// 针对交换机进行测试
@Test
public void exchangeTest(){
// 1. 先构造一个交换机并插入
Exchange expectedExchange = createExchangeTest("testExchange");
memoryDataCenter.insertExchange(expectedExchange);
// 2. 查询这个交换机是否一致, 此处是看引用是否相同
Exchange actualExchange = memoryDataCenter.getExchange("testExchange");
Assertions.assertEquals(expectedExchange,actualExchange);
// 3. 删除这个交换机
memoryDataCenter.deleteExchange(actualExchange.getName());
// 4. 再次查询看看是否查不到了
actualExchange = memoryDataCenter.getExchange("testExchange");
Assertions.assertNull(actualExchange);
}
4.3.2 队列创建删除断言测试
java
/ 针对队列进行测试
@Test
public void queueTest(){
// 1. 构造一个队列并插入
MSGQueue expectedQueue = createMSGQueueTest("testQueue");
memoryDataCenter.insertQueue(expectedQueue);
// 2. 查询这个队列并比较
MSGQueue actualQueue = memoryDataCenter.getQueue("testQueue");
Assertions.assertEquals(expectedQueue,actualQueue);
// 3. 删除这个队列
memoryDataCenter.deleteQueue("testQueue");
// 4. 再次查询这个队列
actualQueue = memoryDataCenter.getQueue("testQueue");;
Assertions.assertNull(actualQueue);
}
4.3.3 绑定创建删除断言测试
java
// 针对绑定进行测试
@Test
public void bindingTest() throws MqException {
// 1. 创建并插入绑定
Binding expectedBinding = new Binding();
expectedBinding.setExchangeName("testExchange");
expectedBinding.setQueueName("testQueue");
expectedBinding.setBindingKey("testBinding");
memoryDataCenter.insertBinding(expectedBinding);
// 2. 查询这个绑定并比较, 还要测试一下 getBindings 这个方法
Binding actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
ConcurrentHashMap<String, Binding> bindingMap = memoryDataCenter.getBindings("testExchange");
Assertions.assertEquals(expectedBinding,actualBinding);
Assertions.assertEquals(1,bindingMap.size());
Assertions.assertEquals(expectedBinding,bindingMap.get("testQueue"));
// 3. 删除绑定
memoryDataCenter.deleteBinding(actualBinding);
actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
Assertions.assertNull(actualBinding);
}
4.3.4 消息创建删除断言测试
java
@Test
public void MessageTest(){
Message expectedMessage = createTestMessage("testMessage");
memoryDataCenter.addMessage(expectedMessage);
Message actualMessage = memoryDataCenter.getMessageById(expectedMessage.getMessageId());
Assertions.assertEquals(expectedMessage,actualMessage);
memoryDataCenter.deleteMessage(actualMessage.getMessageId());
actualMessage = memoryDataCenter.getMessageById(expectedMessage.getMessageId());
Assertions.assertNull(actualMessage);
}
4.3.5 发送消息断言测试
java
@Test
public void sentMessageTest(){
// 1. 创建一个队列, 创建 10 条消息, 把这些消息都插入到队列中
MSGQueue queue = createMSGQueueTest("testQueue");
List<Message> expectedMessages = new LinkedList<>();
for (int i = 0; i < 10; i++) {
Message message = createTestMessage("testMessage" + i);
memoryDataCenter.sendMessage(queue,message);
expectedMessages.add(message);
}
// 2. 从消息队列取出这些消息
List<Message> actualMessages = new LinkedList<>();
while (true) {
Message message = memoryDataCenter.pollMessage("testQueue");
if (message == null){
break;
}
actualMessages.add(message);
}
// 3. 比较取出的消息是否一致
Assertions.assertEquals(expectedMessages.size(),actualMessages.size());
for (int i = 0; i < 10; i++) {
Assertions.assertEquals(expectedMessages.get(i),actualMessages.get(i));
}
}
4.3.6 未应答消息断言测试
java
@Test
public void MessageWaitAckTest(){
Message expectedMessage = createTestMessage("testMessage");
memoryDataCenter.addMessageWaitAck("testQueue",expectedMessage);
Message actualMessage = memoryDataCenter.getMessageWaitAck("testQueue",expectedMessage.getMessageId());
Assertions.assertEquals(expectedMessage,actualMessage);
memoryDataCenter.deleteMessageWaitAck("testQueue",expectedMessage.getMessageId());
actualMessage = memoryDataCenter.getMessageWaitAck("testQueue",expectedMessage.getMessageId());
Assertions.assertNull(actualMessage);
}
4.3.7 重启断言测试
java
@Test
public void recoveryTest() throws IOException, MqException, ClassNotFoundException {
// 由于后续会进行数据库操作, 所以要先启动一下 SpringApplication, 这样才可以进行后续操作
MqApplication.context = SpringApplication.run(MqApplication.class);
// 1. 在硬盘上构造好数据
DiskDataCenter diskDataCenter = new DiskDataCenter();
diskDataCenter.init();
// 构造交换机
Exchange expectedExchange = createExchangeTest("testExchange");
diskDataCenter.insertExchange(expectedExchange);
// 构造队列
MSGQueue expectedQueue = createMSGQueueTest("testQueue");
diskDataCenter.insertQueue(expectedQueue);
// 构造绑定
Binding expectedBinding = new Binding();
expectedBinding.setExchangeName("testExchange");
expectedBinding.setQueueName("testQueue");
expectedBinding.setBindingKey("testBindingKey");
diskDataCenter.insertBinding(expectedBinding);
// 构造消息
Message expectedMessage = createTestMessage("testMessage");
diskDataCenter.sendMessage(expectedQueue,expectedMessage);
// 2. 执行恢复操作
memoryDataCenter.recovery(diskDataCenter);
// 3. 对比结果
Exchange actualExchange = memoryDataCenter.getExchange("testExchange");
Assertions.assertEquals(expectedExchange.getName(),actualExchange.getName());
Assertions.assertEquals(expectedExchange.getType(),actualExchange.getType());
Assertions.assertEquals(expectedExchange.isAutoDelete(),actualExchange.isAutoDelete());
Assertions.assertEquals(expectedExchange.isDurable(),actualExchange.isDurable());
MSGQueue actualQueue = memoryDataCenter.getQueue("testQueue");
Assertions.assertEquals(expectedQueue.getName(),actualQueue.getName());
Assertions.assertEquals(expectedQueue.isDurable(),actualQueue.isDurable());
Assertions.assertEquals(expectedQueue.isAutoDelete(),actualQueue.isAutoDelete());
Assertions.assertEquals(expectedQueue.isExclusive(),actualQueue.isExclusive());
Binding actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
Assertions.assertEquals(expectedBinding.getBindingKey(),actualBinding.getBindingKey());
Assertions.assertEquals(expectedBinding.getExchangeName(),actualBinding.getExchangeName());
Assertions.assertEquals(expectedBinding.getQueueName(),actualBinding.getQueueName());
Message actualMessage = memoryDataCenter.pollMessage("testQueue");
Assertions.assertEquals(expectedMessage.getMessageId(),actualMessage.getMessageId());
Assertions.assertArrayEquals(expectedMessage.getBody(),actualMessage.getBody());
Assertions.assertEquals(expectedMessage.getRoutingKey(),actualMessage.getRoutingKey());
Assertions.assertEquals(expectedMessage.getDeliverMode(),actualMessage.getDeliverMode());
// 4. 清楚硬盘中的数据, 把整个 data 目录里的内容都删掉 (包含 data.db 和队列的目录)
MqApplication.context.close();
File dataDir = new File("./data");
FileUtils.deleteDirectory(dataDir);
}

一次运行全部通过
4.4 转发规则断言测试
这是我设计的测试用例
sql
// [测试用例]
// binding key routing key 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
前后进行处理
java
@BeforeEach
public void setUp(){
binding = new Binding();
message = new Message();
}
@AfterEach
public void tearDown(){
binding = null;
message = null;
}

可以通过所有的测试用例
4.5 虚拟主机断言测试
4.5.0 前期准备
虚拟主机封装了前面的数据库, 硬盘操作以及内存管理操作
先做好前置处理
java
@BeforeEach
public void setUp(){
MqApplication.context = SpringApplication.run(MqApplication.class);
virtualHost = new VirtualHost("default");
}
@AfterEach
public void tearDown() throws IOException {
MqApplication.context.close();
// 删除硬盘的目录
File dataDir = new File("./data");
FileUtils.deleteDirectory(dataDir);
}
4.5.1 发送消息断言测试
java
@Test
public void basicPublishTest() {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIRECT,
true, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueDeclare("testQueue", true, false, false,null);
Assertions.assertTrue(ok);
ok = virtualHost.basicPublish("testExchange","testQueue",null,"hello".getBytes());
}
4.5.2 消费者订阅消息先后顺序断言测试
java
@Test
public void basicConsumeTest1() throws InterruptedException {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIRECT,
true, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueDeclare("testQueue", true, false, false,null);
Assertions.assertTrue(ok);
// 先订阅队列, 后发送消息
ok = virtualHost.basicConsume("testConsumerTag", "testQueue", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
try {
// 消费者自身设定的回调方法
System.out.println("messageId = " + basicProperties.getMessageId());
System.out.println("body = " + new String(body,0,body.length));
Assertions.assertEquals("testQueue",basicProperties.getRoutingKey());
Assertions.assertEquals(1,basicProperties.getDeliverMode());
} catch (Error e) {
// 断言失败抛出的不是Exception 而是 Error
e.printStackTrace();
System.out.println("Error");
}
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
ok = virtualHost.basicPublish("testExchange","testQueue",new BasicProperties(),"hello".getBytes());
Assertions.assertTrue(ok);
}
@Test
public void basicConsumeTest2() throws InterruptedException {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.DIRECT,
true, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueDeclare("testQueue", true, false, false,null);
Assertions.assertTrue(ok);
// 先发送消息, 后订阅队列
ok = virtualHost.basicPublish("testExchange","testQueue",new BasicProperties(),"hello".getBytes());
Assertions.assertTrue(ok);
Thread.sleep(500);
ok = virtualHost.basicConsume("testConsumerTag", "testQueue", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
try {
// 消费者自身设定的回调方法
System.out.println("messageId = " + basicProperties.getMessageId());
System.out.println("body = " + new String(body,0,body.length));
Assertions.assertEquals("testQueue",basicProperties.getRoutingKey());
Assertions.assertEquals(1,basicProperties.getDeliverMode());
} catch (Error e) {
// 断言失败抛出的不是Exception 而是 Error
e.printStackTrace();
System.out.println("Error");
}
}
});
Assertions.assertTrue(ok);
}
4.5.3 FANOUT 交换机订阅消费断言测试
java
@Test
public void basicConsumeFanoutTest() throws InterruptedException {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.FANOUT,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueDeclare("testQueue1", false, false, false,null);
Assertions.assertTrue(ok);
ok = virtualHost.queueBind("testQueue1","testExchange", "");
Assertions.assertTrue(ok);
ok = virtualHost.queueDeclare("testQueue2", false, false, false,null);
Assertions.assertTrue(ok);
ok = virtualHost.queueBind("testQueue2","testExchange", "");
Assertions.assertTrue(ok);
// 往交换机中发布一个消息
virtualHost.basicPublish("testExchange","",null, "hello".getBytes());
Thread.sleep(500);
// 两个消费者订阅一下消息
ok = virtualHost.basicConsume("testConsumerTag1", "testQueue1", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
System.out.println("consumerTag = " + consumerTag);
System.out.println("messageId = " + basicProperties.getMessageId());
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
Assertions.assertTrue(ok);
ok = virtualHost.basicConsume("testConsumerTag2", "testQueue2", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
System.out.println("consumerTag = " + consumerTag);
System.out.println("messageId = " + basicProperties.getMessageId());
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
Assertions.assertTrue(ok);
}
4.5.4 TOPIC 交换机订阅消费断言测试
java
@Test
public void testBasicConsumeTopic() throws InterruptedException {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.TOPIC,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueDeclare("testQueue1", false, false, false,null);
Assertions.assertTrue(ok);
ok = virtualHost.queueBind("testQueue1","testExchange", "aaa.*.bbb");
Assertions.assertTrue(ok);
// 往交换机中发布一个消息
virtualHost.basicPublish("testExchange","aaa.ccc.bbb",null, "hello1".getBytes());
Thread.sleep(500);
// 两个消费者订阅一下消息
ok = virtualHost.basicConsume("testConsumerTag1", "testQueue1", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
System.out.println("consumerTag = " + consumerTag);
System.out.println("messageId = " + basicProperties.getMessageId());
Assertions.assertArrayEquals("hello1".getBytes(), body);
}
});
Assertions.assertTrue(ok);
}
4.5.5 手动应答消息断言测试
java
public boolean basicAck(String queueName, String messageId) {
try {
queueName = virtualHostName + queueName;
// 1. 获取队列和消息
MSGQueue queue = memoryDataCenter.getQueue(queueName);
if (queue == null) {
throw new MqException("[VirtualHost] 要确认的队列不存在! queueName = " + queueName);
}
Message message = memoryDataCenter.getMessageById(messageId);
if (message == null) {
throw new MqException("[VirtualHost] 要确认的队列不存在! messageId = " + messageId);
}
// 2. 删除硬盘的数据
if (message.getDeliverMode() == 2) {
diskDataCenter.deleteMessage(queue,message);
}
// 3. 删除内存的数据
memoryDataCenter.deleteMessage(messageId);
// 4. 删除待确认队列的消息
memoryDataCenter.deleteMessageWaitAck(queueName,messageId);
System.out.println("[VirtualHost] basicAck 成功! 消息确认成功 queueName = " + queueName
+ " messageId = " + messageId);
return true;
} catch (Exception e) {
System.out.println("[VirtualHost] basicAck 失败! 消息确认失败 queueName = " + queueName
+ " messageId = " + messageId);
e.printStackTrace();
return false;
}
}
4.5.6 交换机队列绑定创建上面的已经包括了

直接一次测试全部通过
4.6 客户端断言测试
4.6.0 前期准备
开始, 启动服务器, 并配置一下 ConnectionFactory 客户端的配置
结束, 停止服务器, 关闭数据库, 删掉硬盘上的文件
java
@BeforeEach
public void setUp() throws IOException {
// 1. 先启动服务器
MqApplication.context = SpringApplication.run(MqApplication.class);
brokerServer = new BrokerServer(9090);
// 这个 start 会进入一个死循环, 使用一个新的线程运行 start 即可!
t = new Thread(() -> {
try {
brokerServer.start();
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
// 2. 配置 ConnectionFactory
connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(9090);
}
@AfterEach
public void tearDown() throws IOException, InterruptedException {
// 停止服务器
brokerServer.stop();
// t.join();
MqApplication.context.close();
// 删除必要的文件
File file = new File("./data");
FileUtils.deleteDirectory(file);
connectionFactory = null;
}
4.6.1 创建连接和信道断言测试
java
@Test
public void ConnectionTest() throws IOException {
Connection connection = connectionFactory.newConnection();
Assertions.assertNotNull(connection);
}
@Test
public void channelTest() throws IOException {
Connection connection = connectionFactory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
}
4.6.2 交换机请求和删除断言测试
java
@Test
public void exchangeTest() throws IOException {
Connection connection = connectionFactory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
boolean ok = channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
Assertions.assertTrue(ok);
ok = channel.exchangeDelete("testExchange");
Assertions.assertTrue(ok);
// 稳妥起见关闭一下
channel.close();
connection.close();
}
4.6.3 队列请求和删除断言测试
java
@Test
public void queueTest() throws IOException {
Connection connection = connectionFactory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
boolean ok = channel.queueDeclare("testQueue",true,false,false,null);
Assertions.assertTrue(ok);
ok = channel.queueDelete("testQueue");
Assertions.assertTrue(ok);
channel.close();
connection.close();
}
4.6.4 绑定请求和删除断言测试
java
@Test
public void bindingTest() throws IOException {
Connection connection = connectionFactory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
boolean ok = channel.queueDeclare("testQueue",true,false,false,null);
Assertions.assertTrue(ok);
ok = channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
Assertions.assertTrue(ok);
ok = channel.queueBind("testQueue","testExchange","testBindingKey");
Assertions.assertTrue(ok);
ok = channel.queueUnBind("testQueue","testExchange");
Assertions.assertTrue(ok);
channel.close();
connection.close();
}
4.6.5 消息发送与订阅断言测试
java
@Test
public void messageTest() throws IOException, MqException, InterruptedException {
Connection connection = connectionFactory.newConnection();
Assertions.assertNotNull(connection);
Channel channel = connection.createChannel();
Assertions.assertNotNull(channel);
boolean ok = channel.queueDeclare("testQueue",true,false,false,null);
Assertions.assertTrue(ok);
ok = channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
Assertions.assertTrue(ok);
ok = channel.basicPublish("testExchange","testQueue",null,"hello1".getBytes());
Assertions.assertTrue(ok);
ok = channel.basicConsume("testQueue", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) throws MqException, IOException {
System.out.println("消费开始");
System.out.println("consumerTag = " + consumerTag);
System.out.println("basicProperties = " + basicProperties);
Assertions.assertArrayEquals(body,"hello1".getBytes());
System.out.println("消费结束");
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
channel.close();
connection.close();
}

一次执行全部成功
4.7 创建服务器和客户端模拟测试
4.7.1 创建消费者类
java
package com.fafu.mq.demo;
import com.fafu.mq.common.Consumer;
import com.fafu.mq.common.MqException;
import com.fafu.mq.mqclient.Channel;
import com.fafu.mq.mqclient.Connection;
import com.fafu.mq.mqclient.ConnectionFactory;
import com.fafu.mq.mqserver.core.BasicProperties;
import com.fafu.mq.mqserver.core.ExchangeType;
import java.io.IOException;
public class DemoConsumer {
public static void main(String[] args) throws IOException, MqException, InterruptedException {
System.out.println("启动消费者");
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(9090);
Connection connection = connectionFactory.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() {
@Override
public 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);
}
}
}
4.7.2 创建生产者类
java
package com.fafu.mq.demo;
import com.fafu.mq.common.Consumer;
import com.fafu.mq.common.MqException;
import com.fafu.mq.mqclient.Channel;
import com.fafu.mq.mqclient.Connection;
import com.fafu.mq.mqclient.ConnectionFactory;
import com.fafu.mq.mqserver.core.BasicProperties;
import com.fafu.mq.mqserver.core.ExchangeType;
import java.io.IOException;
public class DemoConsumer {
public static void main(String[] args) throws IOException, MqException, InterruptedException {
System.out.println("启动消费者");
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(9090);
Connection connection = connectionFactory.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() {
@Override
public 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);
}
}
}
4.7.3 打开服务器, 并运行这两个类
先启动我们的 BrokerServer

然后启动生产者

最后启动消费者

都可以正常读取数据, 没有发生报错

BrokerServer 也正常打印日志