仿RabbitMQ实现消息队列-服务端核心模块实现(3)

消息管理模块

一、整体架构设计

我采用 三层职责分离架构,这是高并发消息队列存储层的标准设计:

职责
MessageManager 多队列管理、创建/销毁队列、安全查找、细粒度锁
QueueMessage 单队列消息生命周期管理、内存索引、待确认队列、GC 触发
MessageMapper 磁盘文件 IO、消息读写、GC 数据重整、原子文件替换

架构优势:

  1. 锁粒度极小,高并发无竞争
  2. 内存/磁盘分层,速度极快
  3. GC 异步化,不阻塞业务
  4. 重启自动恢复,消息不丢失

二、高并发锁设计

核心思想:只锁共享数据,不锁业务逻辑

我在代码中使用 最小临界区设计

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 流程:

  1. 加载所有有效消息
  2. 写入临时文件
  3. 原子替换旧数据文件
  4. 更新内存消息 offset
  5. 磁盘空间立即释放

优势:

  • 不阻塞业务
  • 无数据丢失风险
  • 自动整理磁盘碎片

六、测试用例

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 的核心调度中枢与业务统一入口

它聚合了交换机管理、队列管理、绑定关系管理、消息持久化管理四大核心模块,承担这些职责:

  1. 实现多虚拟主机业务隔离,不同 vhost 下交换机、队列、绑定完全互不干扰;
  2. 统一封装交换机/队列/绑定的声明、删除、查询接口;
  3. 封装消息发布、消息消费、消息 ACK 确认的顶层接口;
  4. 服务启动自动加载历史队列,完成消息持久化重启恢复;
  5. 实现级联资源清理:删交换机连带清绑定、删队列连带清绑定与磁盘消息文件;
  6. 依托下层细粒度锁设计,天然支撑高并发访问场景。

可以理解为:下层是基础存储与原子能力,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 级联资源自动清理

  1. 删除交换机:先清理该交换机所有绑定关系,再删除交换机元数据;
  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 测试覆盖场景

  1. 交换机、队列、绑定关系的声明与存在性校验;
  2. 消息批量生产、顺序消费、无消息时返回空校验;
  3. 删除交换机自动级联清除所有关联绑定;
  4. 删除队列自动级联清绑定、销毁消息存储句柄,无法再消费;
  5. 消息正常消费 + 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.

全部用例通过,模块功能、边界逻辑、资源清理、消息流转全部验证无误。

相关推荐
m0_629494738 小时前
LeetCode 热题 100-----15.轮转数组
数据结构·算法·leetcode
AI科技星8 小时前
从180°旋转定值π、e论证时空宿命与未来可预测性—全域数学视角
人工智能·算法·机器学习·数学建模·数据挖掘
WL_Aurora8 小时前
Python 算法基础篇之栈和队列
python·算法
橙子也要努力变强8 小时前
进程与信号
linux·服务器·c++
艺术电影节8 小时前
惊喜映后 | 伍迪·艾伦经典修复澳门首映
算法·推荐算法·电视
我不是懒洋洋8 小时前
手写一个B+树:从原理到数据库索引实战
c语言·c++·经验分享
奶茶树8 小时前
【STL/数据结构】哈希表和unordered系列容器的封装
开发语言·c++·散列表
Brilliantwxx8 小时前
【C++】初步认识STL(3)
开发语言·c++·笔记·算法
玛丽莲茼蒿8 小时前
Leetcode hot100 螺旋矩阵【中等】
算法·leetcode·矩阵