前言
之前我们已经将,队列,交换机,绑定,存储到了数据库中并测试完毕,而且也将消息存入到了文件中,实现了消息的持久化存储,并且我们将这些都写入了内存,而且还提供了一个给上层调用的接口。而这些内容都是在中间人服务器中的,接下来我们编写重要的一个类,虚拟主机:虚拟主机类似于数据库,不仅仅要管理数据,还要提供一些核心的API供上层调用。主要流程就是将虚拟主机与之前写过的API串联起来
虚拟主机的设计
如何表示交换机与虚拟主机之间的从属关系呢?
假设1:参考数据库的设计,实现一对多的方案,
假设2:重新约定,交换机的名字 = 虚拟主机的名字+交换机的名字;
虚拟主机的目的是为了保证,不同虚拟主机之间的内容互不影响。在这里我们采取假设2的实现,比较简单
实现
创建virtualHost类,在这个类中对交换机,队列,绑定,消息进行管理,同时处理异常
对于虚拟主机我们设计以下属性
主机名称 : virtuaHostName
内存控制类:MemoryDataCenter
硬盘控制类:DiskDataCenter
交换机转发规则类:Router(未实现)
消费者的核心功能:ConsumerManager(未实现)
这里说一下锁:因为我们接下来是对硬盘和文件来进行写入的,所以为了防止多线程情况下出现问题,我们统一加上锁,这里我们设计俩个锁对象
// 操作交换机的锁对象
private final Object exchangeLocker = new Object();
// 操作队列的锁对象
private final Object queueLocker = new Object();
核心方法
- 初始化:初始化内存控制类,与硬盘控制类
public VirtuaHost(String name){
this.virtuaHostName = name;
diskDataCenter.init();
// 如果存在数据, 将硬盘里面的数据, 恢复到内存中
try {
memoryDataCenter.recovery(diskDataCenter);
} catch (IOException | MqException | ClassNotFoundException e) {
e.printStackTrace();
System.out.println("virtualHost 恢复内存数据失败");
}
}
- 创建交换机
public boolean exchangeDeclare(String exchangeName , ExchangeType exchangeType ,
boolean durable, boolean autoDelete , Map<String ,Object> argument){
exchangeName = virtuaHostName+exchangeName;
try {
synchronized (exchangeLocker){
Exchange existsExchange = memoryDataCenter.getExchange(exchangeName);
if (existsExchange != null){
// 该交换机已经存在!
System.out.println("[VirtualHost] 交换机已经存在! exchangeName=" + exchangeName);
return true;
}
Exchange exchange = new Exchange();
exchange.setName(exchangeName);
exchange.setType(exchangeType);
exchange.setDurable(durable);
exchange.setAutoDelete(autoDelete);
exchange.setArguments(argument);
// 写入硬盘
if (durable){
diskDataCenter.insertExchange(exchange);
}
// 写入内存
memoryDataCenter.insertExchange(exchange);
System.out.println("[VirtualHost] 交换机创建完成! exchangeName=" + exchangeName);
// 上述逻辑, 先写硬盘, 后写内存. 目的就是因为硬盘更容易写失败. 如果硬盘写失败了, 内存就不写了.
// 要是先写内存, 内存写成功了, 硬盘写失败了, 还需要把内存的数据给再删掉. 就比较麻烦了.
}
return true;
}catch (Exception e) {
System.out.println("[VirtualHost] 交换机创建失败! exchangeName=" + exchangeName);
e.printStackTrace();
return false;
}
}
- 删除交换机
public boolean exchangeDelete(String exchangeName) {
exchangeName = virtuaHostName+exchangeName;
try {
synchronized (exchangeLocker){
// 1. 先找到对应的交换机.
Exchange existsExchange = memoryDataCenter.getExchange(exchangeName);
if (existsExchange == null){
throw new MqException("[VirtualHost] 交换机不存在无法删除!");
}
// 2 看硬盘中是否有数据
if (existsExchange.isDurable()){
diskDataCenter.deleteExchange(exchangeName);
}
// 3. 删除内存中的交换机数据
memoryDataCenter.deleteExchange(exchangeName);
System.out.println("[VirtualHost] 交换机删除成功! exchangeName=" + exchangeName);
}
return true;
} catch (Exception e) {
System.out.println("[VirtualHost] 交换机删除失败! exchangeName=" + exchangeName);
e.printStackTrace();
return false;
}
}
- 创建队列
// 创建队列
public boolean queueDeclare(String queueName, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments) {
queueName = virtuaHostName+queueName;
try {
synchronized (queueLocker){
// 1查找队列是否存在
MSGQueue existsQueue = memoryDataCenter.getQueue(queueName);
if (existsQueue != null){
return true;
}
// 2. 创建队列对象
MSGQueue queue = new MSGQueue();
queue.setName(queueName);
queue.setDurable(durable);
queue.setExclusive(exclusive);
queue.setAutoDelete(autoDelete);
queue.setArguments(arguments);
// 3. 写硬盘
if (durable) {
diskDataCenter.insertQueue(queue);
}
// 4. 写内存
memoryDataCenter.insertQueue(queue);
System.out.println("[VirtualHost] 队列创建成功! queueName=" + queueName);
}
return true;
} catch (Exception e) {
System.out.println("[VirtualHost] 队列创建失败! queueName=" + queueName);
e.printStackTrace();
return false;
}
}
- 删除队列
// 删除队列
public boolean queueDelete(String queueName) {
queueName = virtuaHostName+queueName;// 删除队列
try{
synchronized (queueLocker){
MSGQueue existsQueue = memoryDataCenter.getQueue(queueName);
if (existsQueue == null){
throw new MqException("[VirtualHost] 队列不存在! 无法删除! queueName=" + queueName);
}
// 2 删除 硬盘数据
if (existsQueue.isDurable()){
diskDataCenter.deleteQueue(queueName);
}
// 3. 删除内存数据
memoryDataCenter.deleteQueue(queueName);
System.out.println("[VirtualHost] 删除队列成功! queueName=" + queueName);
}
return true;
} catch (MqException | IOException e) {
System.out.println("[VirtualHost] 删除队列失败! queueName=" + queueName);
e.printStackTrace();
return false;
}
}
- 创建绑定
// 确认消息的编写
public boolean basicPublish(String exchangeName, String routingKey, BasicProperties basicProperties, byte[] body) {
// 1. 转换交换机的名字
exchangeName = virtuaHostName+exchangeName;
try {
// 2. 检查routingkey是否合法
if (!router.checkRoutingKey(routingKey)){
throw new MqException("[VirtualHost] routingKey 非法! routingKey=" + routingKey);
}
// 3. 查找交换机的看是否存在
Exchange exchange = memoryDataCenter.getExchange(exchangeName);
if (exchange == null){
throw new MqException("[VirtualHost] 交换机不存在! exchangeName=" + exchangeName);
}
// 4.判断交换机的类型
if (exchange.getType() == ExchangeType.direct){
// 按照直接交换机的方式来转发消息
// 以 routingKey 作为队列的名字, 直接把消息写入指定的队列中.
// 此时, 可以无视绑定关系.
String queueName = virtuaHostName+routingKey;
// 5. 构造消息对象
Message message = Message.createMessageWithId(routingKey,basicProperties,body);
// 6. 查找该队列名对应的对象
MSGQueue queue = memoryDataCenter.getQueue(queueName);
if (queue == null){
throw new MqException("[VirtualHost] 队列不存在! queueName=" + queueName);
}
// 7. 队列存在, 直接给队列中写入消息
sendMessage(queue,message);
}else {
// 用其他俩种方式来转发
// 找到交换机 关联的所有绑定, 并遍历这些绑定对象
ConcurrentHashMap<String,Binding> bindingMap = memoryDataCenter.getBindings(exchangeName);
for (Map.Entry<String,Binding> entry:bindingMap.entrySet()) {
Binding binding = entry.getValue();
MSGQueue queue = memoryDataCenter.getQueue(binding.getMsgQueueName());
if (queue == null){
// 此处咱们就不抛出异常了. 可能此处有多个这样的队列.
// 希望不要因为一个队列的失败, 影响到其他队列的消息的传输.
System.out.println("[VirtualHost] basicPublish 发送消息时, 发现队列不存在! queueName=" + binding.getMsgQueueName());
continue;
}
// 构造消息对象
Message message = Message.createMessageWithId(routingKey,basicProperties,body);
// 判断是什么规则 能否转发
if (!router.route(exchange.getType(),binding,message)){
continue;
}
sendMessage(queue,message);
}
}
return true;
} catch (Exception e) {
System.out.println("[VirtualHost] 消息发送失败!");
e.printStackTrace();
return false;
}
}
- 删除绑定
public boolean queueUnbind(String queueName, String exchangeName) {
queueName = virtuaHostName + queueName;
exchangeName = virtuaHostName + exchangeName;
try {
synchronized (exchangeLocker) {
synchronized (queueLocker) {
// 1. 获取 binding 看是否已经存在~
Binding binding = memoryDataCenter.getBinding(exchangeName, queueName);
if (binding == null) {
throw new MqException("[VirtualHost] 删除绑定失败! 绑定不存在! exchangeName=" + exchangeName + ", queueName=" + queueName);
}
// 2. 无论绑定是否持久化了, 都尝试从硬盘删一下. 就算不存在, 这个删除也无副作用.
diskDataCenter.deleteBinding(binding);
// 3. 删除内存的数据
memoryDataCenter.deleteBinding(binding);
System.out.println("[VirtualHost] 删除绑定成功!");
}
}
return true;
} catch (Exception e) {
System.out.println("[VirtualHost] 删除绑定失败!");
e.printStackTrace();
return false;
}
}
- 订阅消息
// 订阅消息.
// 添加一个队列的订阅者, 当队列收到消息之后, 就要把消息推送给对应的订阅者.
// consumerTag: 消费者的身份标识
// autoAck: 消息被消费完成后, 应答的方式. 为 true 自动应答. 为 false 手动应答.
// consumer: 是一个回调函数. 此处类型设定成函数式接口. 这样后续调用 basicConsume 并且传实参的时候, 就可以写作 lambda 样子了.
public boolean basicConsume(String consumerTag, String queueName, boolean autoAck, Consumer consumer) {
// 构造一个ConsumEnv对象, 将这个对象的队列找到, 然后将consume 添加到这个队列里面
queueName = virtuaHostName+ queueName;
try {
consumerManager.addConsumer(consumerTag,queueName,autoAck,consumer);
System.out.println("[VirtualHost] basicConsume 成功! queueName=" + queueName);
return true;
} catch (Exception e) {
System.out.println("[VirtualHost] basicConsume 失败! queueName=" + queueName);
e.printStackTrace();
return false;
}
}
- 确认ACK
public boolean basicAck(String queueName, String messageId) {
queueName = virtuaHostName + queueName;
try {
// 1. 获取到消息和队列
Message message = memoryDataCenter.getMessage(messageId);
if (message == null) {
throw new MqException(“[VirtualHost] 要确认的消息不存在! messageId=” + messageId);
}
MSGQueue queue = memoryDataCenter.getQueue(queueName);
if (queue == null) {
throw new MqException(“[VirtualHost] 要确认的队列不存在! queueName=” + queueName);
}
// 2. 删除硬盘上的数据
if (message.getDeliverMode() == 2) {
diskDataCenter.deleteMessage(queue, message);
}
// 3. 删除消息中心中的数据
memoryDataCenter.removeMessage(messageId);
// 4. 删除待确认的集合中的数据
memoryDataCenter.removeMessageWaitAck(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;
}
}
- 确认消息
// 确认消息的编写
public boolean basicPublish(String exchangeName, String routingKey, BasicProperties basicProperties, byte[] body) {
// 1. 转换交换机的名字
exchangeName = virtuaHostName+exchangeName;
try {
// 2. 检查routingkey是否合法
if (!router.checkRoutingKey(routingKey)){
throw new MqException("[VirtualHost] routingKey 非法! routingKey=" + routingKey);
}
// 3. 查找交换机的看是否存在
Exchange exchange = memoryDataCenter.getExchange(exchangeName);
if (exchange == null){
throw new MqException("[VirtualHost] 交换机不存在! exchangeName=" + exchangeName);
}
// 4.判断交换机的类型
if (exchange.getType() == ExchangeType.direct){
// 按照直接交换机的方式来转发消息
// 以 routingKey 作为队列的名字, 直接把消息写入指定的队列中.
// 此时, 可以无视绑定关系.
String queueName = virtuaHostName+routingKey;
// 5. 构造消息对象
Message message = Message.createMessageWithId(routingKey,basicProperties,body);
// 6. 查找该队列名对应的对象
MSGQueue queue = memoryDataCenter.getQueue(queueName);
if (queue == null){
throw new MqException("[VirtualHost] 队列不存在! queueName=" + queueName);
}
// 7. 队列存在, 直接给队列中写入消息
sendMessage(queue,message);
}else {
// 用其他俩种方式来转发
// 找到交换机 关联的所有绑定, 并遍历这些绑定对象
ConcurrentHashMap<String,Binding> bindingMap = memoryDataCenter.getBindings(exchangeName);
for (Map.Entry<String,Binding> entry:bindingMap.entrySet()) {
Binding binding = entry.getValue();
MSGQueue queue = memoryDataCenter.getQueue(binding.getMsgQueueName());
if (queue == null){
// 此处咱们就不抛出异常了. 可能此处有多个这样的队列.
// 希望不要因为一个队列的失败, 影响到其他队列的消息的传输.
System.out.println("[VirtualHost] basicPublish 发送消息时, 发现队列不存在! queueName=" + binding.getMsgQueueName());
continue;
}
// 构造消息对象
Message message = Message.createMessageWithId(routingKey,basicProperties,body);
// 判断是什么规则 能否转发
if (!router.route(exchange.getType(),binding,message)){
continue;
}
sendMessage(queue,message);
}
}
return true;
} catch (Exception e) {
System.out.println("[VirtualHost] 消息发送失败!");
e.printStackTrace();
return false;
}
}
private void sendMessage(MSGQueue queue, Message message) throws IOException, MqException, InterruptedException {
// 此处发送消息就是将消息写入到硬盘 和内存之间
int delivermode = message.getDeliverMode();
// 判断是否要持久化
if (delivermode == 2){
diskDataCenter.sendMessage(queue,message);
}
// 写入内存
memoryDataCenter.sendMessage(queue,message);
// 通知消费者可以消费消息了
consumerManager.notifyConsume(queue.getName());
}
虚拟主机测试
package com.example.demo.mqServer.dataCenter;
import com.example.demo.Common.Consumer;
import com.example.demo.MqApplication;
import com.example.demo.mqServer.VirtuaHost;
import com.example.demo.mqServer.core.BasicProperties;
import com.example.demo.mqServer.core.ExchangeType;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.File;
import java.io.IOException;
@SpringBootTest
public class VirtualHostTests {
private VirtuaHost virtualHost = null;
@BeforeEach
public void setUp() {
MqApplication.context = SpringApplication.run(MqApplication.class);
virtualHost = new VirtuaHost("default");
}
@AfterEach
public void tearDown() throws IOException {
MqApplication.context.close();
virtualHost = null;
// 把硬盘的目录删除掉
File dataDir = new File("./data");
FileUtils.deleteDirectory(dataDir);
}
@Test
public void testExchangeDeclare() {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.direct,
true, false, null);
Assertions.assertTrue(ok);
}
@Test
public void testExchangeDelete() {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.direct,
true, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.exchangeDelete("testExchange");
Assertions.assertTrue(ok);
}
@Test
public void testQueueDeclare() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
}
@Test
public void testQueueDelete() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueDelete("testQueue");
Assertions.assertTrue(ok);
}
@Test
public void testQueueBind() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.direct,
true, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueBind("testQueue", "testExchange", "testBindingKey");
Assertions.assertTrue(ok);
}
@Test
public void testQueueUnbind() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.direct,
true, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueBind("testQueue", "testExchange", "testBindingKey");
Assertions.assertTrue(ok);
ok = virtualHost.queueUnbind("testQueue", "testExchange");
Assertions.assertTrue(ok);
}
@Test
public void testBasicPublish() {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.direct,
true, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.basicPublish("testExchange", "testQueue", null,
"hello".getBytes());
Assertions.assertTrue(ok);
}
// 先订阅队列, 后发送消息
@Test
public void testBasicConsume1() throws InterruptedException {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.direct,
true, false, null);
Assertions.assertTrue(ok);
// 先订阅队列
ok = virtualHost.basicConsume("testConsumerTag", "testQueue", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
// 消费者自身设定的回调方法.
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());
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
// 再发送消息
ok = virtualHost.basicPublish("testExchange", "testQueue", null,
"hello".getBytes());
Assertions.assertTrue(ok);
}
// 先发送消息, 后订阅队列.
@Test
public void testBasicConsume2() throws InterruptedException {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.direct,
true, false, null);
Assertions.assertTrue(ok);
// 先发送消息
ok = virtualHost.basicPublish("testExchange", "testQueue", null,
"hello".getBytes());
Assertions.assertTrue(ok);
// 再订阅队列
ok = virtualHost.basicConsume("testConsumerTag", "testQueue", true, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
// 消费者自身设定的回调方法.
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());
Assertions.assertArrayEquals("hello".getBytes(), body);
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
}
@Test
public void testBasicConsumeFanout() 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);
// 往交换机中发布一个消息
ok = virtualHost.basicPublish("testExchange", "", null, "hello".getBytes());
Assertions.assertTrue(ok);
Thread.sleep(500);
// 两个消费者订阅上述的两个队列.
ok = virtualHost.basicConsume("testConsumer1", "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("testConsumer2", "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);
Thread.sleep(500);
}
@Test
public void testBasicConsumeTopic() throws InterruptedException {
boolean ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.topic, false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueDeclare("testQueue", false, false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.queueBind("testQueue", "testExchange", "aaa.*.bbb");
Assertions.assertTrue(ok);
ok = virtualHost.basicPublish("testExchange", "aaa.ccc.bbb", null, "hello".getBytes());
Assertions.assertTrue(ok);
ok = virtualHost.basicConsume("testConsumer", "testQueue", 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);
Thread.sleep(500);
}
@Test
public void testBasicAck() throws InterruptedException {
boolean ok = virtualHost.queueDeclare("testQueue", true,
false, false, null);
Assertions.assertTrue(ok);
ok = virtualHost.exchangeDeclare("testExchange", ExchangeType.direct,
true, false, null);
Assertions.assertTrue(ok);
// 先发送消息
ok = virtualHost.basicPublish("testExchange", "testQueue", null,
"hello".getBytes());
Assertions.assertTrue(ok);
// 再订阅队列 [要改的地方, 把 autoAck 改成 false]
ok = virtualHost.basicConsume("testConsumerTag", "testQueue", false, new Consumer() {
@Override
public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) {
// 消费者自身设定的回调方法.
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());
Assertions.assertArrayEquals("hello".getBytes(), body);
// [要改的地方, 新增手动调用 basicAck]
boolean ok = virtualHost.basicAck("testQueue", basicProperties.getMessageId());
Assertions.assertTrue(ok);
}
});
Assertions.assertTrue(ok);
Thread.sleep(500);
}
}