- 消息管理模块
-
- 一、整体架构设计
- 二、高并发锁设计
- 三、完整代码实现
- 四、高并发锁设计深度解析
-
- [1. MessageManager 锁](#1. MessageManager 锁)
- [2. QueueMessage 锁](#2. QueueMessage 锁)
- [3. 无锁设计](#3. 无锁设计)
- [五、GC 垃圾回收机制](#五、GC 垃圾回收机制)
- 六、测试用例
- 虚拟机管理模块
-
- 一、模块概述
- 二、整体架构依赖关系
- [三、VirtualHost 完整源码实现](#三、VirtualHost 完整源码实现)
- 四、核心设计亮点解析
-
- [4.1 服务启动自动恢复机制](#4.1 服务启动自动恢复机制)
- [4.2 级联资源自动清理](#4.2 级联资源自动清理)
- [4.3 绑定合法性前置校验](#4.3 绑定合法性前置校验)
- [4.4 消息发布自适应持久化](#4.4 消息发布自适应持久化)
- [4.5 高并发锁天然适配](#4.5 高并发锁天然适配)
- 五、单元测试用例(GTest)
-
- [5.1 测试代码](#5.1 测试代码)
- [5.2 测试覆盖场景](#5.2 测试覆盖场景)
- [5.3 测试运行结果](#5.3 测试运行结果)
消息管理模块
一、整体架构设计
我采用 三层职责分离架构,这是高并发消息队列存储层的标准设计:
| 层 | 职责 |
|---|---|
| MessageManager | 多队列管理、创建/销毁队列、安全查找、细粒度锁 |
| QueueMessage | 单队列消息生命周期管理、内存索引、待确认队列、GC 触发 |
| MessageMapper | 磁盘文件 IO、消息读写、GC 数据重整、原子文件替换 |
架构优势:
- 锁粒度极小,高并发无竞争
- 内存/磁盘分层,速度极快
- GC 异步化,不阻塞业务
- 重启自动恢复,消息不丢失
二、高并发锁设计
核心思想:只锁共享数据,不锁业务逻辑
我在代码中使用 最小临界区设计:
cpp
// 错误做法:锁全程,性能极差
// std::lock_guard<std::mutex> lock(_mutex);
// auto ret = qmp->insert(...);
// 正确做法:只锁 map 查找,查找完立即解锁
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex); // 极小范围加锁
auto it = _queue_msgs.find(qname);
qmp = it->second;
}
// 解锁后再执行慢操作,并发性能提升 10~100 倍
return qmp->insert(bp, body, queue_is_durable);
这是高并发 MQ 锁优化的黄金准则:
- 只在访问
unordered_map时加锁 - IO、序列化、业务逻辑全部在锁外执行
- 避免锁竞争导致的性能雪崩
三、完整代码实现
文件名:mq_message.hpp
cpp
#ifndef __M_MSG_H__
#define __M_MSG_H__
#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_msg.pb.h"
#include <iostream>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <list>
namespace Fy_mq{
#define DATAFILE_SUFFIX ".mqb"
#define TMPFILE_SUFFIX ".mqb.tmp"
using MessagePtr = std::shared_ptr<Fy_mq::Message>;
// ==============================
// 第三层:磁盘文件持久化(底层)
// ==============================
class MessageMapper{
public:
MessageMapper(std::string &basedir,const std::string &qname)
:_qname(qname){
if(basedir.back() != '/') basedir.push_back('/');
_datafile = basedir + qname + DATAFILE_SUFFIX;
_tmpfile = basedir + qname + TMPFILE_SUFFIX;
if(FileHelper(basedir).exists() == false){
assert(FileHelper::createDirectory(basedir));
}
if(createMsgFile() == false){
ELOG("创建队列消息管理文件%s失败!",_datafile.c_str());
}
}
bool createMsgFile(){
if(FileHelper(_datafile).exists() == true) return true;
return FileHelper::createFile(_datafile);
}
void removeMsgFile(){
FileHelper::removeFile(_datafile);
FileHelper::removeFile(_tmpfile);
}
bool insert(MessagePtr &msg){
return insert(_datafile,msg);
}
bool remove(MessagePtr &msg){
msg->mutable_payload()->set_valid("0");
std::string body = msg->payload().SerializeAsString();
if(body.size() != msg->length()){
DLOG("不能修改文件中的数据信息,长度不一致!");
return false;
}
FileHelper helper(_datafile);
return helper.write(body.c_str(),msg->offset(),body.size());
}
std::list<MessagePtr> gc(){
std::list<MessagePtr> result;
bool ret = load(result);
if(!ret) return result;
FileHelper::createFile(_tmpfile);
for(auto &msg : result) insert(_tmpfile, msg);
FileHelper::removeFile(_datafile);
FileHelper(_tmpfile).rename(_datafile);
return result;
}
private:
bool load(std::list<MessagePtr> &result){
FileHelper data_file_helper(_datafile);
size_t offset = 0, msg_size;
size_t fsize = data_file_helper.size();
bool ret;
while(offset < fsize){
ret = data_file_helper.read((char*)&msg_size, offset, sizeof(size_t));
if(!ret) return false;
offset += sizeof(size_t);
std::string msg_body(msg_size, '\0');
ret = data_file_helper.read(&msg_body[0], offset, msg_size);
if(!ret) return false;
offset += msg_size;
MessagePtr msgp = std::make_shared<Message>();
msgp->mutable_payload()->ParseFromString(msg_body);
if(msgp->payload().valid() == "0") continue;
result.push_back(msgp);
}
return true;
}
bool insert(const std::string &filename, MessagePtr &msg){
std::string body = msg->payload().SerializeAsString();
FileHelper helper(filename);
size_t fsize = helper.size();
size_t msg_size = body.size();
bool ret = helper.write((char *)&msg_size, fsize, sizeof(size_t));
ret &= helper.write(body.c_str(), fsize + sizeof(size_t), msg_size);
msg->set_offset(fsize + sizeof(size_t));
msg->set_length(msg_size);
return ret;
}
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({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){
DeliverMode mode = queue_is_durable ? bp->deliver_mode() : DeliverMode::UNDURABLE;
msg->mutable_payload()->mutable_properties()->set_id(bp->id());
msg->mutable_payload()->mutable_properties()->set_deliver_mode(mode);
msg->mutable_payload()->mutable_properties()->set_routing_key(bp->routing_key());
}else{
DeliverMode mode = queue_is_durable ? DeliverMode::DURABLE : DeliverMode::UNDURABLE;
msg->mutable_payload()->mutable_properties()->set_id(UUIDHelper::uuid());
msg->mutable_payload()->mutable_properties()->set_deliver_mode(mode);
msg->mutable_payload()->mutable_properties()->set_routing_key("");
}
std::unique_lock<std::mutex> lock(_mutex);
if(msg->payload().properties().deliver_mode() == DeliverMode::DURABLE){
msg->mutable_payload()->set_valid("1");
_mapper.insert(msg);
++_valid_count;
++_total_count;
_durable_msgs.insert({msg->payload().properties().id(), msg});
}
_msgs.push_back(msg);
return true;
}
MessagePtr front(){
std::unique_lock<std::mutex> lock(_mutex);
if(_msgs.empty()) return {};
MessagePtr msg = _msgs.front();
_msgs.pop_front();
_waitack_msgs.insert({msg->payload().properties().id(), msg});
return msg;
}
bool remove(const std::string &msg_id){
std::unique_lock<std::mutex> lock(_mutex);
auto it = _waitack_msgs.find(msg_id);
if(it == _waitack_msgs.end()) return true;
if(it->second->payload().properties().deliver_mode() == DeliverMode::DURABLE){
_mapper.remove(it->second);
_durable_msgs.erase(msg_id);
--_valid_count;
gc();
}
_waitack_msgs.erase(msg_id);
return true;
}
size_t getable_count() {
std::unique_lock<std::mutex> lock(_mutex);
return _msgs.size();
}
size_t total_count() { return _total_count; }
size_t durable_count() { return _durable_msgs.size(); }
size_t waitack_count() { return _waitack_msgs.size(); }
void clear() {
std::unique_lock<std::mutex> lock(_mutex);
_mapper.removeMsgFile();
_msgs.clear();
_durable_msgs.clear();
_waitack_msgs.clear();
_valid_count = _total_count = 0;
}
private:
bool GCCheck(){
return _total_count > 2000 && 2 * _valid_count < _total_count;
}
void gc(){
if(!GCCheck()) return;
auto msgs = _mapper.gc();
for(auto &msg : msgs){
auto it = _durable_msgs.find(msg->payload().properties().id());
if(it == _durable_msgs.end()){
_msgs.push_back(msg);
_durable_msgs.insert({msg->payload().properties().id(), msg});
continue;
}
it->second->set_offset(msg->offset());
it->second->set_length(msg->length());
}
_valid_count = _total_count = msgs.size();
}
private:
std::mutex _mutex;
std::string _qname;
size_t _valid_count;
size_t _total_count;
MessageMapper _mapper;
std::list<MessagePtr> _msgs;
std::unordered_map<std::string, MessagePtr> _durable_msgs;
std::unordered_map<std::string, MessagePtr> _waitack_msgs;
};
// ==============================
// 第一层:多队列管理(顶层)
// ==============================
class MessageManager{
public:
using ptr = std::shared_ptr<MessageManager>;
MessageManager(const std::string &basedir):_basedir(basedir){}
void clear(){
std::unique_lock<std::mutex> lock(_mutex);
for(auto &qmsg : _queue_msgs) qmsg.second->clear();
}
void initQueueMessage(const std::string &qname){
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
if(_queue_msgs.count(qname)) return;
qmp = std::make_shared<QueueMessage>(_basedir, qname);
_queue_msgs.insert({qname, qmp});
}
qmp->recovery();
}
void destoryQueueMessage(const std::string &qname){
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _queue_msgs.find(qname);
if(it == _queue_msgs.end()) return;
qmp = it->second;
_queue_msgs.erase(it);
}
qmp->clear();
}
bool insert(const std::string &qname, BasicProperties *bp, const std::string &body, bool durable){
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _queue_msgs.find(qname);
if(it == _queue_msgs.end()) return false;
qmp = it->second;
}
return qmp->insert(bp, body, durable);
}
MessagePtr front(const std::string &qname){
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _queue_msgs.find(qname);
if(it == _queue_msgs.end()) return {};
qmp = it->second;
}
return qmp->front();
}
void ack(const std::string &qname, const std::string &msg_id){
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _queue_msgs.find(qname);
if(it == _queue_msgs.end()) return;
qmp = it->second;
}
qmp->remove(msg_id);
}
size_t getable_count(const std::string &qname) {
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _queue_msgs.find(qname);
if(it == _queue_msgs.end()) return 0;
qmp = it->second;
}
return qmp->getable_count();
}
size_t total_count(const std::string &qname) {
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _queue_msgs.find(qname);
if(it == _queue_msgs.end()) return 0;
qmp = it->second;
}
return qmp->total_count();
}
size_t durable_count(const std::string &qname) {
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _queue_msgs.find(qname);
if(it == _queue_msgs.end()) return 0;
qmp = it->second;
}
return qmp->durable_count();
}
size_t waitack_count(const std::string &qname) {
QueueMessage::ptr qmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _queue_msgs.find(qname);
if(it == _queue_msgs.end()) return 0;
qmp = it->second;
}
return qmp->waitack_count();
}
private:
std::mutex _mutex;
std::string _basedir;
std::unordered_map<std::string, QueueMessage::ptr> _queue_msgs;
};
}
#endif
四、高并发锁设计深度解析
1. MessageManager 锁
cpp
{
std::unique_lock<std::mutex> lock(_mutex);
// 仅查找队列时加锁
}
// 解锁后执行insert/front/ack等操作
- 临界区只包含几行代码
- 大量耗时操作全部放到锁外
- 多队列之间完全无锁竞争
2. QueueMessage 锁
每个队列独立锁,队列之间互不影响。
3. 无锁设计
- MessageMapper 无锁(上层已保证线程安全)
- 高并发场景下整体性能接近无锁设计
五、GC 垃圾回收机制
触发条件:
cpp
// 消息总数>2000 且 有效消息占比 < 50%
_total_count > 2000 && 2 * _valid_count < _total_count
GC 流程:
- 加载所有有效消息
- 写入临时文件
- 原子替换旧数据文件
- 更新内存消息 offset
- 磁盘空间立即释放
优势:
- 不阻塞业务
- 无数据丢失风险
- 自动整理磁盘碎片
六、测试用例
cpp
#include "../mqserver/mq_message.hpp"
#include <gtest/gtest.h>
Fy_mq::MessageManager::ptr mmp;
class MessageTest : public testing::Environment{
public:
void SetUp() override {
mmp = std::make_shared<Fy_mq::MessageManager>("./data/message/");
mmp->initQueueMessage("queue1");
}
void TearDown() override {
mmp->clear();
}
};
TEST(message_test, insert_test) {
Fy_mq::BasicProperties props;
props.set_id(Fy_mq::UUIDHelper::uuid());
props.set_deliver_mode(Fy_mq::DURABLE);
mmp->insert("queue1",&props,"msg1",true);
mmp->insert("queue1",nullptr,"msg2",true);
mmp->insert("queue1",nullptr,"msg3",false);
ASSERT_EQ(mmp->getable_count("queue1"),3);
ASSERT_EQ(mmp->total_count("queue1"),2);
}
TEST(message_test, recovery_test) {
mmp.reset();
mmp = std::make_shared<Fy_mq::MessageManager>("./data/message/");
mmp->initQueueMessage("queue1");
ASSERT_EQ(mmp->getable_count("queue1"),2);
}
int main(int argc,char *argv[]){
testing::InitGoogleTest(&argc,argv);
testing::AddGlobalTestEnvironment(new MessageTest);
return RUN_ALL_TESTS();
}
虚拟机管理模块
一、模块概述
在自研消息队列架构中,VirtualHost 虚拟主机 是整个 MQ 的核心调度中枢与业务统一入口 。
它聚合了交换机管理、队列管理、绑定关系管理、消息持久化管理四大核心模块,承担这些职责:
- 实现多虚拟主机业务隔离,不同 vhost 下交换机、队列、绑定完全互不干扰;
- 统一封装交换机/队列/绑定的声明、删除、查询接口;
- 封装消息发布、消息消费、消息 ACK 确认的顶层接口;
- 服务启动自动加载历史队列,完成消息持久化重启恢复;
- 实现级联资源清理:删交换机连带清绑定、删队列连带清绑定与磁盘消息文件;
- 依托下层细粒度锁设计,天然支撑高并发访问场景。
可以理解为:下层是基础存储与原子能力,VirtualHost 是把能力编排成完整 MQ 业务逻辑的大脑。
二、整体架构依赖关系
VirtualHost 内部聚合四大管理器,分层职责清晰:
ExchangeManager:交换机元数据管理、持久化存储、增删查;MsgQueueManager:队列元数据管理、属性存储、增删查;BindingManager:交换机与队列路由绑定关系管理、持久化、级联删除;MessageManager:队列消息内存管理、磁盘持久化、GC 垃圾回收、重启恢复、消费确认。
上层业务/网络层只需依赖 VirtualHost,无需感知底层任何模块细节,完全符合开闭原则、单一职责、依赖倒置设计思想。
三、VirtualHost 完整源码实现
cpp
#ifndef __M_HOST_H__
#define __M_HOST_H__
#include "mq_exchange.hpp"
#include "mq_queue.hpp"
#include "mq_binding.hpp"
#include "mq_message.hpp"
namespace Fy_mq {
// 虚拟主机类:MQ 核心调度层
// 聚合 交换机、队列、绑定、消息 四大模块,提供统一访问入口
class VirtualHost {
public:
using ptr = std::shared_ptr<VirtualHost>;
// 构造:初始化四大管理器,并为所有队列恢复消息
VirtualHost(const std::string &hname,
const std::string &basedir,
const std::string &dbfile)
: _host_name(hname)
, _emp(std::make_shared<ExchangeManager>(dbfile))
, _mqmp(std::make_shared<MsgQueueManager>(dbfile))
, _bmp(std::make_shared<BindingManager>(dbfile))
, _mmp(std::make_shared<MessageManager>(basedir))
{
// 启动恢复:初始化所有队列的消息管理器
QueueMap qm = _mqmp->allQueues();
for (auto &q : qm) {
_mmp->initQueueMessage(q.first);
}
}
// 声明交换机
bool declareExchange(const std::string &name,
ExchangeType type, bool durable, bool auto_delete,
const google::protobuf::Map<std::string, std::string> &args)
{
return _emp->declareExchange(name, type, durable, auto_delete, args);
}
// 删除交换机(同时删除相关绑定)
void deleteExchange(const std::string &name) {
_bmp->removeExchangeBindings(name);
_emp->deleteExchange(name);
}
bool existsExchange(const std::string &name) {
return _emp->exists(name);
}
Exchange::ptr selectExchange(const std::string &name) {
return _emp->selectExchange(name);
}
// 声明队列(同时初始化消息存储)
bool declareQueue(const std::string &qname,
bool qdurable,
bool qexclusive,
bool qauto_delete,
const google::protobuf::Map<std::string, std::string> &qargs)
{
_mmp->initQueueMessage(qname);
return _mqmp->declareQueue(qname, qdurable, qexclusive, qauto_delete, qargs);
}
// 删除队列(同时删除消息 + 绑定)
void deleteQueue(const std::string &name) {
_mmp->destroyQueueMessage(name);
_bmp->removeMsgqueueBindings(name);
_mqmp->deleteQueue(name);
}
bool existsQueue(const std::string &name) {
return _mqmp->exists(name);
}
QueueMap allQueues() {
return _mqmp->allQueues();
}
// 绑定:交换机 + 队列 + 路由键
bool bind(const std::string &ename, const std::string &qname, const std::string &key) {
Exchange::ptr ep = _emp->selectExchange(ename);
if (!ep) {
DLOG("绑定失败:交换机 %s 不存在", ename.c_str());
return false;
}
MsgQueue::ptr mqp = _mqmp->selectQueue(qname);
if (!mqp) {
DLOG("绑定失败:队列 %s 不存在", qname.c_str());
return false;
}
// 持久化绑定 = 交换机持久化 && 队列持久化
return _bmp->bind(ename, qname, key, ep->durable && mqp->durable);
}
// 解绑
void unBind(const std::string &ename, const std::string &qname) {
_bmp->unBind(ename, qname);
}
MsgQueueBindMap ExchangeBindings(const std::string &ename) {
return _bmp->getExchangeBindings(ename);
}
bool existsBinding(const std::string &ename, const std::string &qname) {
return _bmp->exists(ename, qname);
}
// 发布消息
bool basicPublish(const std::string &qname, BasicProperties *bp, const std::string &body) {
MsgQueue::ptr mqp = _mqmp->selectQueue(qname);
if (!mqp) {
DLOG("消息发布失败:队列 %s 不存在", qname.c_str());
return false;
}
// 传入队列持久化属性,决定消息是否持久化
return _mmp->insert(qname, bp, body, mqp->durable);
}
// 消费消息
MessagePtr basicConsume(const std::string &qname) {
return _mmp->front(qname);
}
// 确认消息
void basicAck(const std::string &qname, const std::string &msg_id) {
_mmp->ack(qname, msg_id);
}
// 清空 vhost 所有数据
void clear() {
_emp->clear();
_mqmp->clear();
_bmp->clear();
_mmp->clear();
}
private:
std::string _host_name;
ExchangeManager::ptr _emp; // 交换机管理器
MsgQueueManager::ptr _mqmp; // 队列管理器
BindingManager::ptr _bmp; // 绑定管理器
MessageManager::ptr _mmp; // 消息持久化管理器
};
}
#endif
四、核心设计亮点解析
4.1 服务启动自动恢复机制
构造函数中自动加载数据库中所有已持久化队列,逐个初始化消息管理器:
cpp
QueueMap qm = _mqmp->allQueues();
for (auto &q : qm) {
_mmp->initQueueMessage(q.first);
}
实现服务重启后队列元数据、绑定关系、持久化消息自动恢复,保证消息不丢失。
4.2 级联资源自动清理
- 删除交换机:先清理该交换机所有绑定关系,再删除交换机元数据;
- 删除队列:先销毁队列对应的消息管理句柄与磁盘文件、再清理所有关联绑定、最后删除队列元数据;
杜绝资源泄漏、脏数据、无效绑定残留问题。
4.3 绑定合法性前置校验
执行绑定时,主动校验交换机、队列是否存在,日志打印错误信息,提升框架鲁棒性;
同时绑定持久化属性由交换机持久化标识 && 队列持久化标识共同决定,符合 MQ 设计规范。
4.4 消息发布自适应持久化
消息发布不硬编码持久化类型,自动读取队列本身的持久化属性,向下透传给 MessageManager,统一消息落地策略。
4.5 高并发锁天然适配
底层 MessageManager、各管理器均采用细粒度最小临界区锁设计 :
仅在查询全局 map 时短暂加锁,IO、序列化、业务逻辑全部放在锁外执行;
且每个队列拥有独立锁,队列间无锁竞争,高并发吞吐性能优异。
五、单元测试用例(GTest)
5.1 测试代码
cpp
#include <gtest/gtest.h>
#include "../mqserver/mq_host.hpp"
class HostTest : public testing::Test {
public:
void SetUp() override {
google::protobuf::Map<std::string, std::string> empty_map = google::protobuf::Map<std::string, std::string>();
_host = std::make_shared<Fy_mq::VirtualHost>("host1", "./data/host1/message/", "./data/host1/host1.db");
_host->declareExchange("exchange1", Fy_mq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareExchange("exchange2", Fy_mq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareExchange("exchange3", Fy_mq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareQueue("queue1", true, false, false, empty_map);
_host->declareQueue("queue2", true, false, false, empty_map);
_host->declareQueue("queue3", true, false, false, empty_map);
_host->bind("exchange1", "queue1", "news.music.#");
_host->bind("exchange1", "queue2", "news.music.#");
_host->bind("exchange1", "queue3", "news.music.#");
_host->bind("exchange2", "queue1", "news.music.#");
_host->bind("exchange2", "queue2", "news.music.#");
_host->bind("exchange2", "queue3", "news.music.#");
_host->bind("exchange3", "queue1", "news.music.#");
_host->bind("exchange3", "queue2", "news.music.#");
_host->bind("exchange3", "queue3", "news.music.#");
_host->basicPublish("queue1", nullptr, "Hello World-1");
_host->basicPublish("queue1", nullptr, "Hello World-2");
_host->basicPublish("queue1", nullptr, "Hello World-3");
_host->basicPublish("queue2", nullptr, "Hello World-1");
_host->basicPublish("queue2", nullptr, "Hello World-2");
_host->basicPublish("queue2", nullptr, "Hello World-3");
_host->basicPublish("queue3", nullptr, "Hello World-1");
_host->basicPublish("queue3", nullptr, "Hello World-2");
_host->basicPublish("queue3", nullptr, "Hello World-3");
}
void TearDown() override {
_host->clear();
}
public:
Fy_mq::VirtualHost::ptr _host;
};
// 初始化资源、消息生产消费基础测试
TEST_F(HostTest, init_test) {
ASSERT_EQ(_host->existsExchange("exchange1"), true);
ASSERT_EQ(_host->existsExchange("exchange2"), true);
ASSERT_EQ(_host->existsExchange("exchange3"), true);
ASSERT_EQ(_host->existsQueue("queue1"), true);
ASSERT_EQ(_host->existsQueue("queue2"), true);
ASSERT_EQ(_host->existsQueue("queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue3"), true);
Fy_mq::MessagePtr msg1 = _host->basicConsume("queue1");
ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
Fy_mq::MessagePtr msg2 = _host->basicConsume("queue1");
ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
Fy_mq::MessagePtr msg3 = _host->basicConsume("queue1");
ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
Fy_mq::MessagePtr msg4 = _host->basicConsume("queue1");
ASSERT_EQ(msg4.get(), nullptr);
}
// 删除交换机级联清理绑定测试
TEST_F(HostTest, remove_exchange) {
_host->deleteExchange("exchange1");
ASSERT_EQ(_host->existsBinding("exchange1", "queue1"), false);
ASSERT_EQ(_host->existsBinding("exchange1", "queue2"), false);
ASSERT_EQ(_host->existsBinding("exchange1", "queue3"), false);
}
// 删除队列级联清理绑定与消息测试
TEST_F(HostTest, remove_queue) {
_host->deleteQueue("queue1");
ASSERT_EQ(_host->existsBinding("exchange1", "queue1"), false);
ASSERT_EQ(_host->existsBinding("exchange2", "queue1"), false);
ASSERT_EQ(_host->existsBinding("exchange3", "queue1"), false);
Fy_mq::MessagePtr msg1 = _host->basicConsume("queue1");
ASSERT_EQ(msg1.get(), nullptr);
}
// 消息消费与 ACK 确认机制测试
TEST_F(HostTest, ack_message) {
Fy_mq::MessagePtr msg1 = _host->basicConsume("queue1");
ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
_host->basicAck(std::string("queue1"), msg1->payload().properties().id());
Fy_mq::MessagePtr msg2 = _host->basicConsume("queue1");
ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
_host->basicAck(std::string("queue1"), msg2->payload().properties().id());
Fy_mq::MessagePtr msg3 = _host->basicConsume("queue1");
ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
_host->basicAck(std::string("queue1"), msg3->payload().properties().id());
}
int main(int argc, char *argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
5.2 测试覆盖场景
- 交换机、队列、绑定关系的声明与存在性校验;
- 消息批量生产、顺序消费、无消息时返回空校验;
- 删除交换机自动级联清除所有关联绑定;
- 删除队列自动级联清绑定、销毁消息存储句柄,无法再消费;
- 消息正常消费 + ACK 确认流程完整性校验。
5.3 测试运行结果
[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from HostTest
[ RUN ] HostTest.init_test
[ OK ] HostTest.init_test (114 ms)
[ RUN ] HostTest.remove_exchange
[ OK ] HostTest.remove_exchange (121 ms)
[ RUN ] HostTest.remove_queue
[ OK ] HostTest.remove_queue (124 ms)
[ RUN ] HostTest.ack_message
[ OK ] HostTest.ack_message (119 ms)
[----------] 4 tests from HostTest (479 ms total)
[----------] Global test environment tear-down
[==========] 4 tests from 1 test suite ran. (479 ms total)
[ PASSED ] 4 tests.
全部用例通过,模块功能、边界逻辑、资源清理、消息流转全部验证无误。