在消息队列中,存在一些比较核心的概念
1.交换机 exchange
2.队列 queue
3.绑定 binding
4.消息 message
这些都是存在于 BrokerServer 中的
会在项目的目录中创建 上述四个类
交换机类中的代码
java
@Data
public class Exchange {
//此处使用 name 来作为交换机的身份的唯一标识
private String name;
//交换机类型:直接交换机(DIRECT)、主题交换机(TOPIC)、扇出交换机(FANOUT)
private ExchangeType type = ExchangeType.DIRECT;
//durable 表示该交换机是否需要持久化存储,true 表示需要持久化存储(由硬盘决定),false 表示不需要持久化存储
private boolean durable = false;
//如果当前的交换机,没人使用,就会自动删除,true 表示如果当前的交换机没人使用,就会自动删除,false 表示不会自动删除
//这个属性先放在这里,后续代码中并没有真正的去实现这个功能,但是在RabbitMQ中是有实现的(属于锦上添花,可有可无)
private boolean autoDelete = false;
//arguments(参数/选项) 表示创建交换机是指定的一些额外的参数选项,在这个项目中是并没有实现的,但是在RabbitMQ中是有实现的
//为了把这个 arguments 存储在数据库中,就需要把 Map 转成 json 格式的字符串
private Map<String, Object> arguments = new HashMap<>();
}
1.使用 name 作为交换机中的唯一标识的原因是,本次的消息队列主要是通过去模仿RabbitMQ来进行创作的,在RabbitMQ中的交换机就是通过 String name 作为交换机的唯一标识
2.这这个项目中使用到的交换机主要是有三种分别是:直接交换机(DIRECT),主题交换机(TOPIC),扇出交换机(FANOUT),在这里我们使用枚举的方式通过 数字 0, 1, 2 来分别代表着三种交换机
3.这里里的 durable 是用来表示是否需要进行持久化存储的
4.autoDelete 是用来表示 如果当前交换机没有人使用的话,就会自动删除,这里的"人"指的是客户端的调用方
5.arguments 是用来在创建交换机的时候可能会指定一些额外的参数选项,在后续通过sqlite进行建表操作的时候,还需要把 arguments 转成 json 格式的字符串用来进行存储
交换机枚举类
java
/**
* 通过枚举的方式来表示三种交换机的类型
*/
public enum ExchangeType {
DIRECT(0),
FANOUT(1),
TOPIC(2);
private final int type;
private ExchangeType(int type) {
this.type = type;
}
public int getType() {
return type;
}
}
1.创建了 getType 是用来获取当前交换机的类型的
队列类
java
/**
* 这个类一个存储消息的队列
* MSG => Message
*/
@Data
public class MSGQueue {
//表示队列的身份标识
private String name;
//表示队列是否需要持久化存储,true表示需要持久化存储,false表示不需要持久化存储
private boolean durable = false;
//这个队列是否是排他性的,true表示是排他性(表示这个队列能否别别的消费者使用)的,false表示不是排他性的
//这个 独占 的功能展示 暂不实现
private boolean exclusive = false;
//这个队列是否是自动删除的,true表示是自动删除的,false表示不是自动删除的,没有人使用自动删除(这里的人表示的是消费者)
//暂不实现
private boolean autoDelete = false;
//这个队列的参数选项, 暂不实现
private Map<String, Object> arguments = new HashMap<>();
}
1.这里也同样是使用 name 作为身份的唯一标识,是为了符合 RabbitMQ 的格式
2.durable 和上述的意义是一样的
3.exclusive 表示的是排他性,表示这个队列是否可以别别的消费者使用
4.autoDelete 表示的也是自动删除
5.arguments 表示的是关于队列中的参数选项,在建表的时候也是需要转成 json 字符串用来存储的
绑定类(Binding)
java
/**
* 表示队列和交换机之间的关联关系
*/
@Data
public class Binding {
private String exchangeName;
private String queueName;
//bindingKey 就是在出题,用来匹配使用的
private String bindingKey;
// Binding 这个东西,依附于 Exchange 和 Queue 的
//比如,对于持久化来说, 如果 Exchange 和 Queue 任何一个没有持久化
//你的 Binding 持久化是没有意义的
}
1.绑定类的主要作用就是为了匹配队列和交换机,所以这里需要创建 ExchangeName 和 queueName
2.bindingKey 是用来匹配交换机和队列的"密语"=>交换机:土豆土豆我是地雷 ,队列:地雷地雷我的土豆 => 匹配成功
3.在这个类中是不需要单独创建durable来判读是否需要进行持久话存储的,binding的持久化存储是取决于 queue 和 Exchange 是否都进行了持久化存储,如果都进行了持久化存储binding就自然进行了持久化存储,反之有一方没有进行持久化存储,binding自然也就不会进行持久化存储
消息类
java
/**
* 表示一个要传输的消息
* 一个 Message 是包含两个部分
* 1.属性部分 BasicProperties
* 2.正文部分 byte[](正文是支持二进制数据的)
* 这里的 Message 是需要在网络中传输的,并且也需要写入到文件去的
* 此时我们就需要对 Message 进行序列化和反序列化
* 此处使用 标准库中自带的序列化和反序列化
*/
@Data
public class Message implements Serializable {
//这两个属性数 message 最核心的属性
private BasicProperties basicProperties = new BasicProperties();
private byte[] body;
//下面的属性是辅助用的属性
//Message 后续会存储到文件中(如果持久化的话)
//一个文件中会存储很多的消息,如何找到某个消息,在文件中的具体位置呢?
//使用下面的两个偏移量来进行表示 [offsetBeg, offsetEnd)
//这两个属性是不需要被序列化保存到文件中的,此时消息一旦被写入文件之后,所在的位置就固定了,并不需要单独存储
//这两个属性存在的目的,主要是为了让内存中的 Message 对象,能够快速找到对应的硬盘中的 Message 的位置
private transient long offsetBeg = 0; //消息数据的开头距离文件的开头的位置偏移量(字节)
private transient long offsetEnd = 0; //消息数据的结尾距离文件的开头的位置偏移量(字节)
//使用这个属性表示该消息在文件中是否是有效的信息,(针对文件中的消息,如果删除,使用逻辑删除的方式(标记删除))
//0x1 表示有效,0x0 表示无效
private byte isValid = 0x1;
//创建一个工厂方法,让工厂方法自动的帮我们封装创建 Message 对象的过程
//这个方法中创建的 Message 对象,会自动生成唯一的 MessageId
public static Message createMessageWithId(String routingKey, BasicProperties basicProperties, byte[] body) {
Message message = new Message();
if(basicProperties != null) {
message.setBasicProperties(basicProperties);
}
message.setRoutingKey(routingKey);
//此处生成的 MessageId 以 M- 为前缀方便区分
message.setMessageId("M-" + UUID.randomUUID());
message.body = body;
//此处是把 body 和 basicProperties 先设置出来,这两个是 Message 中的核心内容
//而 offsetBeg, offsetEnd, isValid 则是消息持久化的时候才会用到,再把消息写入到文档之前在进行设定
//此处只是在内存中创建一个 Message 对象
return message;
}
public String getMessageId() {
return basicProperties.getMessageId();
}
public void setMessageId(String messageId) {
basicProperties.setMessageId(messageId);
}
public String getRoutingKey() {
return basicProperties.getRoutingKey();
}
public void setRoutingKey(String routingKey) {
basicProperties.setRoutingKey(routingKey);
}
public int getDeliveryMode() {
return basicProperties.getDeliverMode();
}
public void setDeliveryMode(int mode) {
basicProperties.setDeliverMode(mode);
}
}
1.在消息这个类中主要是分成两个核心部分 basicProperties 和 body 前者中存放的是关于 消息的一些属性,后者这是存放消息的内容部分
BasicProperties 类
java
@Data
public class BasicProperties implements Serializable {
//消息的唯一身份标识,此处为了保证 id 的唯一性,使用 UUID 来作为 Message 的id
//UUID 是编程语言中生成唯一标识的一组方法
private String messageId;
//是一个消息上带有的内容,和 bindingKey 做匹配
//如果当前的交换机类型是 DIRECT,此时的 routingKey 就是表示要转发的队列的列名
//如果当前的交换机类型是 FANOUT,此时的 routingKey 无意义
//如果当前的交换机类型是 TOPIC,此时 routingKey 就要和 bindingKey做匹配,符合要求的才能转发给对应的队列
private String routingKey;
//这个属性标识消息的持久化模式,1表示持久化,2表示不持久化
private int deliverMode = 1;
}
1.这里的routingKey 就是和做匹配用的
本次消息队列中使用到的数据库选择的是sqlite,这个数据库相比于MySQL更加的轻量化,更适合这个项目
通过 MetaMapper.xml来对sqlite进行自动化建表操作
java
/**
* 元数据,元属性
*/
@Mapper
public interface MetaMapper {
//提供三个建表方法
void createExchangeTable();
void createQueueTable();
void createBindingTable();
}
1,上述是要创建的三张表
java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zcfe.mq.mqserver.mapper.MetaMapper">
<update id="createExchangeTable">
create table if not exists exchange (
name varchar(50) primary key,
type int,
durable boolean,
autoDelete boolean,
arguments varchar(1024)
);
</update>
<update id="createQueueTable">
create table if not exists queue (
name varchar(50) primary key,
durable boolean,
exclusive boolean,
autoDelete boolean,
arguments varchar(1024)
);
</update>
<update id="createBindingTable">
create table if not exists binding (
exchangeName varchar(50),
queueName varchar(50),
bindingKey varchar(256)
);
</update>
</mapper>
这里通过自动换进行创建三张表,需要值得注意的就是 arguments 在这里是通过字符串的格式来进行存储的,所以对于源代码中的Map就要进行序列化操作转成json字符串