@TOC
目录
虚拟机模块
要管理的数据
- 交换机数据管理句柄
- 队列数据管理句柄
- 绑定信息数据管理模块
- 消息数据管理模块
要管理的操作
- 声明 / 删除交换机:在删除交换机的时候,要删除相关的绑定信息
- 声明 / 删除队列 :在删除队列的时候,要删除相关的绑定信息以及消息数据
- 队列的绑定 / 解绑 :绑定的时候,必须交换机和队列是存在的
- 获取指定队列的消息
- 对指定队列的指定消息进行确认
- 获取交换机相关的所有绑定信息:一条消息要发布给指定交换机的时候,交换机获取所有绑定信息,来确认消息要发布到哪个队列
基于这个,那我们先完成虚拟机模块管理的相关数据
消息管理模块
syntax = "proto3";
package mymq;
enum ExchangeType # 交换类型
{
UNKNOWTYPE = 0;
DIRECT = 1;
FANOUT = 2;
TOPIC = 3;
};
enum DeliveryMode # 是否持久化
{
UNKNOWMODE = 0;
UNDURABLE = 1;
DURABLE = 2;
};
message BasicProperties #消息的属性
{
string id = 1; # 消息ID, UUID
DeliveryMode delivery_mode = 2; # 持久化模块
string rounting_key = 3; # routing_key绑定信息
};
message Message
{
message Payload
{
BasicProperties properties = 1; # 消息属性
string body = 2; # 有效载荷数据
string valid = 3; # 消息是否有效
};
Payload payload = 1; # 真正持久化的只有这一个字段
uint32 offset = 2; # 消息的位置
uint32 length = 3; # 消息的长度
};
要管理的数据
消息信息
- ID:消息的唯一标识
- 持久化标识:表示是否对消息进行持久化(还取决于队列的持久化标志)
- routing_key:决定了当前消息要发布的队列(消息发布到交换机后,根据绑定队列binding_key决定是否发布到指定队列)
消息主体
- 存储偏移量:消息以队列为单元存储在文件中,这个偏移量,是当前消息相对于文件起始位置的偏移量
- 消息长度:从偏移量位置取出指定长度的消息(解决了粘包问题)
- 是否有效标志:标识当前消息是否已经删除(删除一条消息,并不是用后面的消息覆盖掉前面的数据,而是重置有效标志位,当一个文件中的有效消息占据总消息比例不到50%,且数据量超过2000,则进行垃圾回收,重新整理文件数据存储)
当系统重启时,也需要重新加载有效消息即可。
消息的管理
管理方法
以队列为单元进行管理(因为消息的所有操作都是以队列为单元)
管理数据
- 消息链表
- 待确定消息hash
- 持久化消息hash
- 持久化的有效消息数据
- 持久化的总的消息数据
管理操作
- 向队列新增消息
- 获取队首消息
- 对消息进行确认
- 恢复队列历史消息
- 垃圾回收
- 删除队列相关消息文件
队列消息管理
- 初始化队列消息结构
- 移除队列消息结果
- 向队列新增消息
- 对队列消息进行确认
- 恢复队列历史消息
#ifndef __M_MSG_H__
#define __M_MSG_H__
#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"
#include <iostream>
#include <mutex>
#include <memory>
#include <unordered_map>
#include <list>
namespace mymq
{
// 消息的持久化管理
// a. 管理数据
// i. 队列消息⽂件存储的路径
// ii. 队列消息的存储⽂件名
// iii. 队列消息的临时交换⽂件名
// b. 管理操作
// i. ⽇志消息存储在⽂件中(4B⻓度+(属性+内容+有效位)序列化消息,连续存储即可)
// ii. 提供队列消息⽂件创建/删除功能
// iii. 提供队列消息的新增持久化/删除持久化
// iv. 提供持久化内容的垃圾回收(其实就是重新加载出所有有效消息返回,并重新⽣成新的消息
// 存储⽂件)
#define DATAFILE_SUBFIX ".mqd"
#define TMPFILE_SUBFIX ".mqb.tmp"
using MessagePtr = std::shared_ptr<mymq::Message>;
// 消息持久化
class MessageMapper
{
public:
MessageMapper(std::string &basedir, const std::string &qname)
: _qname(qname)
{
if (basedir.back() != '/')
{
basedir.push_back('/');
}
_datafile = basedir + qname + DATAFILE_SUBFIX;
_tmpfile = basedir + qname + TMPFILE_SUBFIX;
if (FileHelper(basedir).exists() == false)
{
FileHelper::createDirectory(basedir);
}
createMsgFile();
}
bool createMsgFile()
{
if (FileHelper(_datafile).exists() == true)
{
return true;
}
bool ret = FileHelper::createFile(_datafile);
if (ret == false)
{
ELOG("创建队列数据文件:%s 失败", _datafile.c_str());
return false;
}
return true;
}
void removeMsgFile()
{
FileHelper::removeFile(_datafile);
FileHelper::removeFile(_tmpfile);
}
bool insert(MessagePtr &msg)
{
return insert(_datafile, msg);
}
bool remove(MessagePtr &msg)
{
// 1. 将msg中的有效标志位修改为‘0’
msg->mutable_payload()->set_valid("0");
// 2,对msg进行序列化
std::string body = msg->SerializeAsString();
if (body.size() != msg->length())
{
ELOG("不能修改文件中的数据信息,因为生成的数据与原数据长度不一样");
return false;
}
// 3. 将序列化后的消息,写入到数据在文件中的指定位置(覆盖原有的数据)
FileHelper helper(_datafile);
bool ret = helper.write(body.c_str(), msg->offset(), body.size());
if (ret == false)
{
DLOG("向队列数据文件写入失败!");
return false;
}
return true;
}
std::list<MessagePtr> gc()
{
std::list<MessagePtr> result;
bool ret = load(result);
if (ret == false)
{
ELOG("加载有效数据失败\n");
return result;
}
// 2. 将有效数据,进行序列化存储到临时文件中
FileHelper::createFile(_tmpfile);
for (auto e : result)
{
DLOG("向临时文件中添加数据 : %s", e->payload().body().c_str());
ret = insert(_tmpfile, e);
if (ret == false)
{
ELOG("向临时文件中添加数据失败");
return result;
}
}
// 3. 删除源文件
ret = FileHelper::removeFile(_datafile);
if (ret == false)
{
ELOG("删除原文件失败");
return result;
}
// 4. 修改临时文件的名字,改成原文件的名称
ret = FileHelper(_tmpfile).rename(_datafile.c_str());
if (ret == false)
{
ELOG("修改文件名称失败");
return result;
}
// 5. 返回新的有效数据
return result;
}
private:
bool load(std::list<MessagePtr> &result)
{
// 1. 加载出文件中的所有的有效数据:存储格式:4字节长度 | 数据 | 4字节长度 | 数据 ......
FileHelper data_file_helper(_datafile);
size_t offset = 0; // 用来定位
size_t msg_size = 0; // 用来 4 字节长度的数据长度
size_t file_size = data_file_helper.size();
while (offset < file_size)
{
bool ret = data_file_helper.read((char*)&msg_size, offset, sizeof(size_t));
if (ret == false)
{
DLOG("读取消息长度失败!");
}
offset += sizeof(size_t);
std::string msg_body(msg_size, '\0');
ret = data_file_helper.read(&msg_body[0], offset, msg_size);
// 为什么使用 &msg_body[0]
// &msg_body[0] 返回的是指向 std::string 内部字符数组首元素的指针,即指向字符串内容的指针。
// 适用于需要直接操作字符串内容的场景,例如文件读写操作
// &msg_body:
// &msg_body 返回的是 std::string 对象本身的指针,即指向 std::string 对象的指针。
// 不适用于直接操作字符串内容的场景,而是用于操作整个 std::string 对象本身。
if (ret == false)
{
DLOG("读取消息数据失败");
return false;
}
offset += msg_size;
MessagePtr msgp = std::make_shared<Message>();
msgp->mutable_payload()->ParseFromString(msg_body);
// 如果是无效消息,则直接处理下一个
if (msgp->payload().valid() == "0")
{
ELOG("加载到无效消息:%s", msgp->payload().body().c_str());
continue;
}
// 有效消息则保存下来
result.push_back(msgp);
}
return true;
}
bool insert(const std::string &filename, MessagePtr &msg)
{
// 新增数据都是添加在文件末尾的
// 1. 进行消息的序列化,获取到格式化后的消息
// 因为使用的是 sqlite 数据库,存储的是二进制信息,所以需要进行序列化
std::string body = msg->payload().SerializeAsString();
// 2. 获取文件长度
FileHelper helper(filename);
size_t fsize = helper.size();
size_t msg_size = body.size();
// 写入逻辑:1. 先写入 4 字节数据长度,2. 再写入指定长度数据
bool ret = helper.write((char *)&msg_size, fsize, sizeof(size_t));
if (ret == false)
{
DLOG("向队列数据文件写入数据长度失败!");
return false;
}
// 将数据写入文件的指定位置
ret = helper.write(body.c_str(), fsize + sizeof(size_t), msg_size);
if (ret == false)
{
DLOG("向队列数据文件写入数据失败!");
return false;
}
// 更新msg中的实际存储信息
msg->set_offset(fsize + sizeof(size_t));
msg->set_length(msg_size);
return true;
}
private:
std::string _qname;
std::string _datafile;
std::string _tmpfile;
};
// 管理数据
class QueueMessage
{
public:
using ptr = std::shared_ptr<QueueMessage>;
// 初始化
QueueMessage(std::string &basedir, const std::string &qname)
: _mapper(basedir, qname),
_qname(qname),
_valid_count(0),
_total_count(0)
{}
bool recovery()
{
// 恢复历史消息
std::unique_lock<std::mutex> lock(_mutex);
_msgs = _mapper.gc();
for (auto &msg : _msgs)
{
_durable_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
}
_valid_count = _total_count = _msgs.size();
return true;
}
bool insert(const BasicProperties *bp, const std::string &body, bool queue_is_durable)
{
// 构造消息对象
MessagePtr msg = std::make_shared<Message>();
msg->mutable_payload()->set_body(body);
if (bp != nullptr)
{
DeliveryMode mode = queue_is_durable ? bp->delivery_mode() : DeliveryMode::UNDURABLE;
msg->mutable_payload()->mutable_properties()->set_id(bp->id());
msg->mutable_payload()->mutable_properties()->set_delivery_mode(mode);
msg->mutable_payload()->mutable_properties()->set_rounting_key(bp->rounting_key());
}
else
{
DeliveryMode mode = queue_is_durable ? DeliveryMode::DURABLE : DeliveryMode::UNDURABLE;
msg->mutable_payload()->