持久化数据管理中心模块

文章目录

  • [1. 消息类型定义&交换机类型定义](#1. 消息类型定义&交换机类型定义)
  • [2. 交换机数据管理](#2. 交换机数据管理)
    • [2.1 框架](#2.1 框架)
    • [2.2 完善交换机数据类的接口](#2.2 完善交换机数据类的接口)
    • [2.3 完善交换机数据持久化类的接口](#2.3 完善交换机数据持久化类的接口)
    • [2.4 完善交换机数据管理类接口](#2.4 完善交换机数据管理类接口)
    • [2.5 测试](#2.5 测试)
  • [3. 队列数据管理](#3. 队列数据管理)
    • [3.1 队列数据类](#3.1 队列数据类)
    • [3.2 队列数据持久化类](#3.2 队列数据持久化类)
    • [3.3 队列数据管理类](#3.3 队列数据管理类)
    • [3.4 测试](#3.4 测试)
  • [4. 绑定信息(交换机-队列)管理](#4. 绑定信息(交换机-队列)管理)
    • [4.1 绑定信息类](#4.1 绑定信息类)
    • [4.2 绑定信息数据持久化类](#4.2 绑定信息数据持久化类)
    • [4.3 绑定信息数据管理类](#4.3 绑定信息数据管理类)
    • [4.4 测试](#4.4 测试)
  • [5. 队列消息管理](#5. 队列消息管理)
    • [5.1 消息的持久化管理](#5.1 消息的持久化管理)
      • [5.1.1 核心流程详解](#5.1.1 核心流程详解)
      • [5.1.2 文件管理机制](#5.1.2 文件管理机制)
    • [5.2 消息的管理(以队列为单位进行管理)](#5.2 消息的管理(以队列为单位进行管理))
      • [5.2.1 队列消息管理](#5.2.1 队列消息管理)
      • [5.2.2 消息的总体对外管理](#5.2.2 消息的总体对外管理)
    • [5.3 测试](#5.3 测试)

1. 消息类型定义&交换机类型定义

在开始正式项目功能模块代码编写之前,我们需要先提前做一件事情,就是将消息类型定义出来。

而消息最终是需要进行持久化存储的,因此涉及到数据的序列化和反序列化,因此消息的类型定义我们使用 protobuf 来进行生成。

因此定义消息类型,其实就是定义一个消息类型的 proto 文件,并生成相关代码。

消息所需要素:

  • 消息本身要素:
    • 消息属性:消息属性中包含有以下内容
      • 消息 ID
      • 消息投递模式:非持久化模式/持久化模式
      • 消息的 routing_key
    • 消息有效载荷内容
  • 消息额外存储所需要素:
    • 消息的存储位置
    • 消息的长度
    • 消息是否有效:注意这里并不使用 bool 类型,而是使用字符的 0/1,因为 bool 类型在持久化的时候所占长度不同,会导致,修改文件中消息有效位后消息长度发生变化,因此不用 bool 类型。

因为客户端与服务端都会用到交换机的一些相关信息,比如交换机类型,还有就是消息的持久化模式,因此我们将交换机类型的枚举,与消息投递模式的枚举也顺便同时定义到 proto 文件中。

  1. 交换机类型
  • DIRECT
  • FANOUT
  • TOPIC
  1. 消息投递模式
  • UNDURABLE:在 RabbitMQ 中,此模式的值为 1,咱们也效仿
  • DURABLE :值为 2.
protobuf 复制代码
syntax = "proto3";

package rabbitmq;

enum ExchangeType {
    UNKNOWTYPE = 0;
    DIRECT = 1; //直接交换
    FANOUT = 2; //广播交换
    TOPIC = 3; //主题交换
}

enum DeliveryMode{
    UNKNOWMODE = 0;
    UNDURABLE = 1; // 非持久化模式
    DURABLE = 2; // 持久化模式
}

// 消息基本属性------单独定义在外面因为后面需要用到
message BasicProperties {
    string id = 1; //消息 ID
    string routing_key = 2; //与 routing_key 做匹配
    DeliveryMode delivery_mode = 3; //持久化模式 1-非持久化; 2-持久化
};

// 消息
message Message {
    // 消息有效载荷------后面不需要用到直接定义在消息的内部
    message Payload {
        BasicProperties properties = 1;//消息属性
        string body = 2;//有效载荷数据
        string valid = 3;//消息是否有效位
    }
    Payload payload = 1;//真正持久化的只有这一个字段
    uint64 offset = 2;//这两个字段用于记录消息在持久化文件中的位置和长度
    uint64 length = 3;//以便于在加载时可以在指定位置读取指定长度的数据获取到消息
}

编译运行:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqcommon$ protoc --cpp_out=./ msg.proto
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqcommon$ ls
helper.hpp  logger.hpp  msg.pb.cc  msg.pb.h  msg.proto

2. 交换机数据管理

  1. 定义交换机数据类
  • 交换机名称
  • 交换机类型
  • 是否持久化标志
  • 是否自动删除标志 (项目中并没有实现这个功能,可扩展)
  • 其他参数 (可扩展)
  1. 定义交换机数据持久化类(数据持久化的 sqlite3 数据库中)
  • 创建/删除交换机数据表
  • 新增交换机数据
  • 移除交换机数据
  • 查询所有交换机数据
  • 查询指定交换机数据(根据名称)
  1. 定义交换机数据管理类
  • 声明交换机,并添加管理(存在则 OK,不存在则创建) 、
  • 删除交换机
  • 获取指定交换机
  • 销毁所有交换机数据

2.1 框架

cpp 复制代码
#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"
#include <iostream>
#include <unordered_map>
#include <memory>
#include <mutex>

namespace rabbitmq
{
    //1. 定义交换机数据类
    struct Exchange
    {
        using ptr = std::shared_ptr<Exchange>;
        std::string _name; // 交换机名称
        ExchangeType _type; // 交换机类型
        bool _durable; // 是否持久化标志
        bool _auto_delete; // 是否自动删除标志
        google::protobuf::Map<std::string, std::string> _args; // 其他参数

		Exchange() {}
        Exchange(const std::string& name, ExchangeType type, 
            bool durable, bool auto_delete, 
            const google::protobuf::Map<std::string, std::string> &args)
            :_name(name), _type(type), _durable(durable), _auto_delete(auto_delete), _args(args)
        {}

        //args 存储键值对,在存储数据库的时候,会组织一个格式字符串进行存储 key=val&key=val....
        //内部解析 str_args 字符串,将内容存储到成员中
        void setArgs(const std::string &str_args);
        //将 args 中的内容进行序列化后,返回一个字符串
        std::string getArgs();
    };

    using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;
    // 2. 定义交换机数据持久化类--数据存储在sqlite数据库中
    class ExchangeMapper
    {
    public:
        ExchangeMapper(const std::string& dbfile):_sql_helper(dbfile)
        {}

        // 创建交换机数据表
        void createTable();
        // 删除交换机数据表
        void removeTable();
        // 新增交换机数据 
        void insert(Exchange::ptr &exp);
        // 移除交换机数据
        void remove(const std::string& name);
        // 查询所有交换机数据,以恢复历史数据
        ExchangeMap recovery();
    private:
        SqliteHelper _sql_helper;
    };

    // 3. 定义交换机数据管理类
    class ExchangeManager
    {
    public:
        ExchangeManager(const std::string& dbfile):_mapper(dbfile)
        {}

        //声明交换机
        bool declareExchange(const std::string &name,
            ExchangeType type, bool durable, bool auto_delete,
            const google::protobuf::Map<std::string, std::string> &args);

        // 删除交换机
        void deleteExchange(const std::string& name);

        // 获取指定交换机对象
        Exchange::ptr selectExchange(const std::string &name);
        // 判断交换机是否存在
        bool exists(const std::string& name);
        // 清理所有交换机数据
        void clear();
    private:
        std::mutex _mutex;
        ExchangeMapper _mapper;
        ExchangeMap _exchanges;
    };
}

由于项目中大部分情况使用不到查询指定交换机数据,完整的Rabbit MQ项目很庞大且我们尽量以最小代价完成项目的主要功能,所以我们没有实现一些功能,当然后续可以作为扩展来实现和完善这些功能。


2.2 完善交换机数据类的接口

cpp 复制代码
	//1. 定义交换机数据类
    struct Exchange
    {
        using ptr = std::shared_ptr<Exchange>;
        std::string _name; // 交换机名称
        ExchangeType _type; // 交换机类型
        bool _durable; // 是否持久化标志
        bool _auto_delete; // 是否自动删除标志
        google::protobuf::Map<std::string, std::string> _args; // 其他参数
        
		Exchange() {}
        Exchange(const std::string& name, ExchangeType type, 
            bool durable, bool auto_delete, 
            const google::protobuf::Map<std::string, std::string> &args)
            :_name(name), _type(type), _durable(durable), _auto_delete(auto_delete), _args(args)
        {}

        //args 存储键值对,在存储数据库的时候,会组织一个格式字符串进行存储 key=val&key=val....
        //内部解析 str_args 字符串,将内容存储到成员中
        void setArgs(const std::string &str_args)
        {
            // key=val&key=val....
            std::vector<std::string> sub_args;
            StringHelper::split(str_args, "&", sub_args);
            for(auto& str : sub_args)
            {
                size_t pos = str.find("=");
                std::string key = str.substr(0, pos);
                std::string val = str.substr(pos + 1);
                _args[key] = val;
            }
        }
        //将 args 中的内容进行序列化后,返回一个字符串
        std::string getArgs()
        {
            std::string result;
            for(auto start = _args.begin(); start != _args.end(); ++start)
            {
                result += start->first + "=" + start->second + "&";
            }
            return result;
        }
    };

2.3 完善交换机数据持久化类的接口

cpp 复制代码
	using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;
    // 2. 定义交换机数据持久化类--数据存储在sqlite数据库中
    class ExchangeMapper
    {
    public:
        ExchangeMapper(const std::string& dbfile):_sql_helper(dbfile)
        {
            std::string path = FileHelper::parentDirectory(dbfile);
            FileHelper::createDirectory(path);
            assert(_sql_helper.open());
            createTable();
        }

        // 创建交换机数据表
        void createTable()
        {
            #define CREATE_TABLE "create table if not exists exchange_table(\
                    name varchar(32) primary key, \
                    type int, \
                    durable int, \
                    auto_delete int, \
                    args varchar(128));"
            bool ret = _sql_helper.exec(CREATE_TABLE, nullptr, nullptr);
            if (ret == false) 
            {
                DLOG("创建交换机数据库表失败!!");
                abort();//直接异常退出程序
            }
        }
        // 删除交换机数据表
        void removeTable()
        {
            #define DROP_TABLE "drop table if exists exchange_table;"
            bool ret = _sql_helper.exec(DROP_TABLE, nullptr, nullptr);
            if (ret == false) 
            {
                DLOG("删除交换机数据库表失败!!");
                abort();//直接异常退出程序
            }
        }
        // 新增交换机数据 
        bool insert(Exchange::ptr &exp)
        {
            std::stringstream ss;
            ss << "insert into exchange_table values(";
            ss << "'" << exp->_name << "', ";
            ss << exp->_type << ", ";
            ss << exp->_durable << ", ";
            ss << exp->_auto_delete << ", ";
            ss << "'" << exp->getArgs() << "');";
            return _sql_helper.exec(ss.str(), nullptr, nullptr);
        }
        // 移除交换机数据
        void remove(const std::string& name)
        {
            std::stringstream ss;
            ss << "delete from exchange_table where name=";
            ss << "'" << name << "';";
            _sql_helper.exec(ss.str(), nullptr, nullptr);
        }
        // 查询所有交换机数据, 以恢复历史数据
        ExchangeMap recovery()
        {
            ExchangeMap result;
            std::string sql = "select name, type, durable, auto_delete, args from exchange_table;";
            _sql_helper.exec(sql, selectCallback, &result);
            return result;
        }
    private:
        // 查询语句会执行这个回调函数
        static int selectCallback(void* arg,int numcol,char** row,char** fields)
        {
            // 回调函数会把查询到的数据存放在哈希表中(输出型参数arg)
            ExchangeMap* result = (ExchangeMap*)arg;
            auto exp = std::make_shared<Exchange>();
            exp->_name = row[0];
            exp->_type = (rabbitmq::ExchangeType)std::stoi(row[1]);
            exp->_durable = (bool)std::stoi(row[2]);
            exp->_auto_delete = (bool)std::stoi(row[3]);
            if (row[4]) exp->setArgs(row[4]);
            result->insert(std::make_pair(exp->_name, exp));
            return 0;
        }
    private:
        SqliteHelper _sql_helper;
    };

2.4 完善交换机数据管理类接口

cpp 复制代码
	// 3. 定义交换机数据管理类
    class ExchangeManager
    {
    public:
		using ptr = std::shared_ptr<ExchangeManager>;
        ExchangeManager(const std::string& dbfile):_mapper(dbfile)
        {
            // 恢复交换机历史数据
            _exchanges = _mapper.recovery();
        }

        //声明交换机
        bool declareExchange(const std::string &name,
            ExchangeType type, bool durable, bool auto_delete,
            const google::protobuf::Map<std::string, std::string> &args)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if (it != _exchanges.end()) 
            {
                //如果交换机已经存在,那就直接返回,不需要重复新增。
                return true;
            }
            auto exp = std::make_shared<Exchange>(name, type, durable, auto_delete, args);
            if(durable == true)
            {
                int ret = _mapper.insert(exp);
                if(ret == false) return false;
            }
            _exchanges.insert(std::make_pair(name, exp));
            return true;
        }

        // 删除交换机
        void deleteExchange(const std::string& name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if (it == _exchanges.end()) 
            {
                //如果交换机不存在,那就直接返回
                return;
            }
            // 持久化的交换机数据才存在_mapper中,通过判断可以提高效率
            if(it->second->_durable == true) _mapper.remove(name);
            _exchanges.erase(name);
        }

        // 获取指定交换机对象
        Exchange::ptr selectExchange(const std::string &name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if (it == _exchanges.end()) 
            {
                //如果交换机不存在,那就直接返回
                return Exchange::ptr();
            }
            return it->second;
        }
        // 判断交换机是否存在
        bool exists(const std::string& name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if (it == _exchanges.end()) 
            {
                //如果交换机不存在,那就直接返回
                return false;
            }
            return true;
        }

        size_t size() 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _exchanges.size();
        }
        // 清理所有交换机数据
        void clear()
        {
			std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeTable();
            _exchanges.clear();
        }
    private:
        std::mutex _mutex;
        ExchangeMapper _mapper;
        ExchangeMap _exchanges;
    };

2.5 测试

测试插入和查询

cpp 复制代码
#include "../mqserver/exchange.hpp"
#include <gtest/gtest.h>

rabbitmq::ExchangeManager::ptr emp;

class ExchangeTest : public testing::Environment 
{
    public:
        virtual void SetUp() override {
            emp = std::make_shared<rabbitmq::ExchangeManager>("./data/meta.db");
        }
        virtual void TearDown() override {
            //emp->clear();
            std::cout << "最后的清理!!\n";
        }
};

TEST(exchange_test, insert_test) 
{
    google::protobuf::Map<std::string, std::string> map;
    map["k1"] = "v1";
    map["k2"] = "v2";	
    emp->declareExchange("exchange1", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange2", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange3", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange4", rabbitmq::ExchangeType::DIRECT, true, false, map);
    ASSERT_EQ(emp->size(), 4);
}
TEST(exchange_test, select_test) 
{
    rabbitmq::Exchange::ptr exp = emp->selectExchange("exchange3");
    ASSERT_NE(exp.get(), nullptr);
    ASSERT_EQ(exp->_name, "exchange3");
    ASSERT_EQ(exp->_durable, true);
    ASSERT_EQ(exp->_auto_delete, false);
    ASSERT_EQ(exp->_type, rabbitmq::ExchangeType::DIRECT);
    ASSERT_EQ(exp->getArgs(), std::string("k1=v1&k2=v2&"));
}


int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new ExchangeTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_exchangetest.cpp -o mq_exchangetest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_exchangetest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from exchange_test
[ RUN      ] exchange_test.insert_test
[       OK ] exchange_test.insert_test (7 ms)
[ RUN      ] exchange_test.select_test
[       OK ] exchange_test.select_test (0 ms)
[----------] 2 tests from exchange_test (7 ms total)

[----------] Global test environment tear-down
最后的清理!!
[==========] 2 tests from 1 test suite ran. (11 ms total)
[  PASSED  ] 2 tests.
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
exchange_table
sqlite> select * from exchange_table;
exchange1|1|1|0|k2=v2&k1=v1&
exchange2|1|1|0|k2=v2&k1=v1&
exchange3|1|1|0|k1=v1&k2=v2&
exchange4|1|1|0|k1=v1&k2=v2&
sqlite> .exit

注意:protobuf::Map 的存储顺序是不确定的

  • google::protobuf::Map内部实现类似于哈希表,不保证遍历顺序
  • 插入 {"k1":"v1"}, {"k2":"v2"}时,遍历顺序可能是随机的
  • 这导致数据库存储时出现了两种顺序:
    • exchange1, exchange2: k2=v2&k1=v1&
    • exchange3, exchange4: k1=v1&k2=v2&

我们在TearDown函数中将clear函数注释掉了,是为了方便我们查看数据库中表的数据,我们可以取消注释再运行编译一下,看看最后表有没有被删除

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make clean
rm -rf mq_filetest mq_exchangetest
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_exchangetest.cpp -o mq_exchangetest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_exchangetest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from exchange_test
[ RUN      ] exchange_test.insert_test
[       OK ] exchange_test.insert_test (0 ms)
[ RUN      ] exchange_test.select_test
mq_exchangetest.cpp:37: Failure
Expected equality of these values:
  exp->getArgs()
    Which is: "k2=v2&k1=v1&"
  std::string("k1=v1&k2=v2&")
    Which is: "k1=v1&k2=v2&"
[  FAILED  ] exchange_test.select_test (0 ms)
[----------] 2 tests from exchange_test (0 ms total)

[----------] Global test environment tear-down
最后的清理!!
[==========] 2 tests from 1 test suite ran. (2 ms total)
[  PASSED  ] 1 test.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] exchange_test.select_test

 1 FAILED TEST
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
sqlite> 

由于protobuf::Map 的存储顺序是不确定的,所以这次测试就出现问题了,可以不用去管,我们看到最后我们查看表时,可以看到表已经被删除了

测试删除:

cpp 复制代码
#include "../mqserver/exchange.hpp"
#include <gtest/gtest.h>

rabbitmq::ExchangeManager::ptr emp;

class ExchangeTest : public testing::Environment 
{
    public:
        virtual void SetUp() override {
            emp = std::make_shared<rabbitmq::ExchangeManager>("./data/meta.db");
        }
        virtual void TearDown() override {
            //emp->clear();
            std::cout << "最后的清理!!\n";
        }
};

TEST(exchange_test, insert_test) 
{
    google::protobuf::Map<std::string, std::string> map;
    map["k1"] = "v1";
    map["k2"] = "v2";
    emp->declareExchange("exchange1", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange2", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange3", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange4", rabbitmq::ExchangeType::DIRECT, true, false, map);
    ASSERT_EQ(emp->size(), 4);
}
TEST(exchange_test, select_test) 
{
    rabbitmq::Exchange::ptr exp = emp->selectExchange("exchange3");
    ASSERT_NE(exp.get(), nullptr);
    ASSERT_EQ(exp->_name, "exchange3");
    ASSERT_EQ(exp->_durable, true);
    ASSERT_EQ(exp->_auto_delete, false);
    ASSERT_EQ(exp->_type, rabbitmq::ExchangeType::DIRECT);
    ASSERT_EQ(exp->getArgs(), std::string("k1=v1&k2=v2&"));
}

TEST(exchange_test, remove_test) 
{
    emp->deleteExchange("exchange2");
    rabbitmq::Exchange::ptr exp = emp->selectExchange("exchange2");
    ASSERT_EQ(exp.get(), nullptr);
    ASSERT_EQ(emp->exists("exchange2"), false);
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new ExchangeTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_exchangetest.cpp -o mq_exchangetest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_exchangetest
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from exchange_test
[ RUN      ] exchange_test.insert_test
[       OK ] exchange_test.insert_test (6 ms)
[ RUN      ] exchange_test.select_test
mq_exchangetest.cpp:37: Failure
Expected equality of these values:
  exp->getArgs()
    Which is: "k2=v2&k1=v1&"
  std::string("k1=v1&k2=v2&")
    Which is: "k1=v1&k2=v2&"
[  FAILED  ] exchange_test.select_test (0 ms)
[ RUN      ] exchange_test.remove_test
[       OK ] exchange_test.remove_test (2 ms)
[----------] 3 tests from exchange_test (8 ms total)

[----------] Global test environment tear-down
最后的清理!!
[==========] 3 tests from 1 test suite ran. (11 ms total)
[  PASSED  ] 2 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] exchange_test.select_test

 1 FAILED TEST
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
exchange_table
sqlite> select * from exchange_table;
exchange1|1|1|0|k1=v1&k2=v2&
exchange3|1|1|0|k2=v2&k1=v1&
exchange4|1|1|0|k1=v1&k2=v2&
sqlite> 

可以看到交换机exchange2被删除了

测试恢复:

cpp 复制代码
#include "../mqserver/exchange.hpp"
#include <gtest/gtest.h>

rabbitmq::ExchangeManager::ptr emp;

class ExchangeTest : public testing::Environment 
{
    public:
        virtual void SetUp() override {
            emp = std::make_shared<rabbitmq::ExchangeManager>("./data/meta.db");
        }
        virtual void TearDown() override {
            //emp->clear();
            std::cout << "最后的清理!!\n";
        }
};

// TEST(exchange_test, insert_test) 
// {
//     google::protobuf::Map<std::string, std::string> map;
//     map["k1"] = "v1";
//     map["k2"] = "v2";
//     emp->declareExchange("exchange1", rabbitmq::ExchangeType::DIRECT, true, false, map);
//     emp->declareExchange("exchange2", rabbitmq::ExchangeType::DIRECT, true, false, map);
//     emp->declareExchange("exchange3", rabbitmq::ExchangeType::DIRECT, true, false, map);
//     emp->declareExchange("exchange4", rabbitmq::ExchangeType::DIRECT, true, false, map);
//     ASSERT_EQ(emp->size(), 4);
// }
TEST(exchange_test, select_test) 
{
    ASSERT_EQ(emp->exists("exchange1"), true);
    ASSERT_EQ(emp->exists("exchange2"), false);
    ASSERT_EQ(emp->exists("exchange3"), true);
    ASSERT_EQ(emp->exists("exchange4"), true);
    rabbitmq::Exchange::ptr exp = emp->selectExchange("exchange3");
    ASSERT_NE(exp.get(), nullptr);
    ASSERT_EQ(exp->_name, "exchange3");
    ASSERT_EQ(exp->_durable, true);
    ASSERT_EQ(exp->_auto_delete, false);
    ASSERT_EQ(exp->_type, rabbitmq::ExchangeType::DIRECT);
    ASSERT_EQ(exp->getArgs(), std::string("k1=v1&k2=v2&"));
}

TEST(exchange_test, remove_test) 
{
    emp->deleteExchange("exchange3");
    rabbitmq::Exchange::ptr exp = emp->selectExchange("exchange3");
    ASSERT_EQ(exp.get(), nullptr);
    ASSERT_EQ(emp->exists("exchange3"), false);
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new ExchangeTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_exchangetest.cpp -o mq_exchangetest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_exchangetest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from exchange_test
[ RUN      ] exchange_test.select_test
[       OK ] exchange_test.select_test (0 ms)
[ RUN      ] exchange_test.remove_test
[       OK ] exchange_test.remove_test (2 ms)
[----------] 2 tests from exchange_test (2 ms total)

[----------] Global test environment tear-down
最后的清理!!
[==========] 2 tests from 1 test suite ran. (2 ms total)
[  PASSED  ] 2 tests.
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
exchange_table
sqlite> select * from exchange_table;
exchange1|1|1|0|k1=v1&k2=v2&
exchange4|1|1|0|k1=v1&k2=v2&
sqlite> 

可以看到恢复了历史数据(exchange2被删除),然后在历史数据的基础上又删除了exchange3

最后再来测试一下插入的数据为空会不会出问题:

cpp 复制代码
#include "../mqserver/exchange.hpp"
#include <gtest/gtest.h>

rabbitmq::ExchangeManager::ptr emp;

class ExchangeTest : public testing::Environment 
{
    public:
        virtual void SetUp() override {
            emp = std::make_shared<rabbitmq::ExchangeManager>("./data/meta.db");
        }
        virtual void TearDown() override {
            //emp->clear();
            std::cout << "最后的清理!!\n";
        }
};

TEST(exchange_test, insert_test) 
{
    google::protobuf::Map<std::string, std::string> map;
    // map["k1"] = "v1";
    // map["k2"] = "v2";
    emp->declareExchange("exchange1", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange2", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange3", rabbitmq::ExchangeType::DIRECT, true, false, map);
    emp->declareExchange("exchange4", rabbitmq::ExchangeType::DIRECT, true, false, map);
    ASSERT_EQ(emp->size(), 4);
}
TEST(exchange_test, select_test) 
{
    ASSERT_EQ(emp->exists("exchange1"), true);
    ASSERT_EQ(emp->exists("exchange2"), false);
    ASSERT_EQ(emp->exists("exchange3"), true);
    ASSERT_EQ(emp->exists("exchange4"), true);
    rabbitmq::Exchange::ptr exp = emp->selectExchange("exchange3");
    ASSERT_NE(exp.get(), nullptr);
    ASSERT_EQ(exp->_name, "exchange3");
    ASSERT_EQ(exp->_durable, true);
    ASSERT_EQ(exp->_auto_delete, false);
    ASSERT_EQ(exp->_type, rabbitmq::ExchangeType::DIRECT);
    ASSERT_EQ(exp->getArgs(), std::string("k1=v1&k2=v2&"));
}

TEST(exchange_test, remove_test) 
{
    emp->deleteExchange("exchange3");
    rabbitmq::Exchange::ptr exp = emp->selectExchange("exchange3");
    ASSERT_EQ(exp.get(), nullptr);
    ASSERT_EQ(emp->exists("exchange3"), false);
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new ExchangeTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_exchangetest.cpp -o mq_exchangetest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_exchangetest
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from exchange_test
[ RUN      ] exchange_test.insert_test
[       OK ] exchange_test.insert_test (4 ms)
[ RUN      ] exchange_test.select_test
mq_exchangetest.cpp:32: Failure
Expected equality of these values:
  emp->exists("exchange2")
    Which is: true
  false
[  FAILED  ] exchange_test.select_test (0 ms)
[ RUN      ] exchange_test.remove_test
[       OK ] exchange_test.remove_test (2 ms)
[----------] 3 tests from exchange_test (6 ms total)

[----------] Global test environment tear-down
最后的清理!!
[==========] 3 tests from 1 test suite ran. (7 ms total)
[  PASSED  ] 2 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] exchange_test.select_test

 1 FAILED TEST
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
exchange_table
sqlite> select * from exchange_table;
exchange1|1|1|0|k1=v1&k2=v2&
exchange4|1|1|0|k1=v1&k2=v2&
exchange2|1|1|0|
sqlite> 

这里因为我们又新增了exchange2,所以断言报错。查表可以看到插入的数据为空,没有问题

注意:我们虽然在插入时又新增了exchange2和exchange3,但是我们的删除测试没有注释,所以把新增的exchange3又删除了,所以查表时,表中并没有exchange3。这是正常的


3. 队列数据管理

当前队列数据的管理,本质上是队列描述信息的管理,描述当前服务器上有哪些队列。

  1. 定义队列描述数据类
  • 队列名称
  • 是否持久化标志
  • 是否独占标志 (可扩展)
  • 是否自动删除标志 (可扩展)
  • 其他参数 (可扩展)
  1. 定义队列数据持久化类(数据持久化的 sqlite3 数据库中)
  • 创建/删除队列数据表
  • 新增队列数据
  • 移除队列数据
  • 查询所有队列数据
  1. 定义队列数据管理类
  • 创建队列,并添加管理(存在则 OK,不存在则创建)
  • 删除队列
  • 获取指定队列
  • 获取所有队列
  • 判断指定队列是否存在
  • 获取队列数量
  • 销毁所有队列数据

3.1 队列数据类

cpp 复制代码
	struct MsgQueue
    {
        using ptr = std::shared_ptr<MsgQueue>;
        std::string _name; // 队列名称
        bool _durable; // 是否持久化标志
        bool _exclusive; // 是否独占标志
        bool _auto_delete; // 是否自动删除标志
        google::protobuf::Map<std::string, std::string> _args; // 其他参数

        MsgQueue() {}
        MsgQueue(const std::string& name, bool durable,
            bool exclusive, bool auto_delete, 
            const google::protobuf::Map<std::string, std::string> &args)
            :_name(name), _durable(durable), _exclusive(exclusive), _auto_delete(auto_delete), _args(args)
        {}

        void setArgs(const std::string &str_args)
        {
            std::vector<std::string> sub_args;
            StringHelper::split(str_args, "&", sub_args);
            for(auto& str : sub_args)
            {
                size_t pos = str.find("=");
                std::string key = str.substr(0, pos);
                std::string val = str.substr(pos + 1);
                _args[key] = val;
            }
        }

        std::string getArgs()
        {
            std::string result;
            for(auto start = _args.begin(); start != _args.end(); ++start)
            {
                result += start->first + "=" + start->second + "&";
            }
            return result;
        }
    };

其实和交换机数据类是基本类似的,只需要稍微修改一下


3.2 队列数据持久化类

cpp 复制代码
	using QueueMap = std::unordered_map<std::string, MsgQueue::ptr>;
    class MsgQueueMapper 
    {
    public:
        MsgQueueMapper(const std::string &dbfile):_sql_helper(dbfile) 
        {
            std::string path = FileHelper::parentDirectory(dbfile);
            FileHelper::createDirectory(path);
            _sql_helper.open();
            createTable();
        }
        void createTable() 
        {
            std::stringstream sql;
            sql << "create table if not exists queue_table(";
            sql << "name varchar(32) primary key, ";
            sql << "durable int, ";
            sql << "exclusive int, ";
            sql << "auto_delete int, ";
            sql << "args varchar(128));";
            assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
        }
        void removeTable() 
        {
            std::string sql = "drop table if exists queue_table;";
            _sql_helper.exec(sql, nullptr, nullptr);
        }
        bool insert(MsgQueue::ptr &queue) 
        {
            // insert into queue_table values('queue1', true, false, false, "k1=v1&k2=v2&");
            std::stringstream sql;
            sql << "insert into queue_table values(";
            sql << "'" << queue->_name << "', ";
            sql << queue->_durable << ", ";
            sql << queue->_exclusive << ", ";
            sql << queue->_auto_delete << ", ";
            sql << "'" << queue->getArgs() << "');";
            return _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void remove(const std::string &name) 
        {
            // delete from queue_table where name='queue1';
            std::stringstream sql;
            sql << "delete from queue_table where name=";
            sql << "'" << name << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        QueueMap recovery() 
        {
            QueueMap result;
            std::string sql = "select name, durable, exclusive, auto_delete, args from queue_table;";
            _sql_helper.exec(sql, selectCallback, &result);
            return result;
        }
    private:
        static int selectCallback(void* arg,int numcol,char** row,char** fields) 
        {
            QueueMap *result = (QueueMap*)arg;
            MsgQueue::ptr mqp = std::make_shared<MsgQueue>();
            mqp->_name = row[0];
            mqp->_durable = (bool)std::stoi(row[1]);
            mqp->_exclusive = (bool)std::stoi(row[2]);
            mqp->_auto_delete = (bool)std::stoi(row[3]);
            if (row[4]) mqp->setArgs(row[4]);
            result->insert(std::make_pair(mqp->_name, mqp));
            return 0;
        }
    private:
        SqliteHelper _sql_helper;
    };

这里持久化数据的操作还是和交换机持久化数据一样的


3.3 队列数据管理类

cpp 复制代码
	class MsgQueueManager 
    {
    public:
        using ptr = std::shared_ptr<MsgQueueManager>;
        MsgQueueManager(const std::string &dbfile):_mapper(dbfile)
        {
            _msg_queues = _mapper.recovery();
        }
        bool declareQueue(const std::string &name, 
            bool durable, 
            bool exclusive,
            bool auto_delete,
            const google::protobuf::Map<std::string, std::string> &args) 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _msg_queues.find(name);
            if (it != _msg_queues.end()) 
            {
                return true;
            }
            MsgQueue::ptr mqp = std::make_shared<MsgQueue>();
            mqp->_name = name;
            mqp->_durable = durable;
            mqp->_exclusive = exclusive;
            mqp->_auto_delete = auto_delete;
            mqp->_args = args;
            if (durable == true) 
            {
                bool ret = _mapper.insert(mqp);
                if (ret == false) return false;
            }
            _msg_queues.insert(std::make_pair(name, mqp));
            return true;
        }
        void deleteQueue(const std::string &name) 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _msg_queues.find(name);
            if (it == _msg_queues.end()) 
            {
                return ;
            }
            if (it->second->_durable == true) _mapper.remove(name);
            _msg_queues.erase(name);
        }
        MsgQueue::ptr selectQueue(const std::string &name) 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _msg_queues.find(name);
            if (it == _msg_queues.end()) 
            {
                return MsgQueue::ptr();
            }
            return it->second;
        }
        QueueMap allQueues() 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _msg_queues;
        }
        bool exists(const std::string &name) 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _msg_queues.find(name);
            if (it == _msg_queues.end()) 
            {
                return false;
            }
            return true;
        }
        size_t size() 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _msg_queues.size();
        }
        void clear() 
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeTable();
            _msg_queues.clear();
        }
    private:
        std::mutex _mutex;
        MsgQueueMapper _mapper;
        QueueMap _msg_queues;
    };

3.4 测试

新增、查询、删除测试:

cpp 复制代码
#include "../mqserver/queue.hpp"
#include <gtest/gtest.h>

rabbitmq::MsgQueueManager::ptr mqmp;

class QueueTest : public testing::Environment 
{
    public:
        virtual void SetUp() override 
        {
            mqmp = std::make_shared<rabbitmq::MsgQueueManager>("./data/meta.db");
        }
        virtual void TearDown() override 
        {
            // mqmp->clear();
        }
};

TEST(queue_test, insert_test) 
{
    google::protobuf::Map<std::string, std::string> map;
    map["k1"] = "v1";
    mqmp->declareQueue("queue1", true, false, false, map);
    mqmp->declareQueue("queue2", true, false, false, map);
    mqmp->declareQueue("queue3", true, false, false, map);
    mqmp->declareQueue("queue4", true, false, false, map);
    ASSERT_EQ(mqmp->size(), 4);
}

TEST(queue_test, select_test) 
{
    ASSERT_EQ(mqmp->exists("queue1"), true);
    ASSERT_EQ(mqmp->exists("queue2"), true);
    ASSERT_EQ(mqmp->exists("queue3"), true);
    ASSERT_EQ(mqmp->exists("queue4"), true);

    rabbitmq::MsgQueue::ptr mqp = mqmp->selectQueue("queue3");
    ASSERT_NE(mqp.get(), nullptr);
    ASSERT_EQ(mqp->_name, "queue3");
    ASSERT_EQ(mqp->_durable, true);
    ASSERT_EQ(mqp->_exclusive, false);
    ASSERT_EQ(mqp->_auto_delete, false);
    ASSERT_EQ(mqp->getArgs(), std::string("k1=v1&"));
}

TEST(queue_test, remove_test) 
{
    mqmp->deleteQueue("queue3");
    ASSERT_EQ(mqmp->exists("queue3"), false);
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new QueueTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_queuetest.cpp -o mq_queuetest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_queuetest
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from queue_test
[ RUN      ] queue_test.insert_test
[       OK ] queue_test.insert_test (2 ms)
[ RUN      ] queue_test.select_test
[       OK ] queue_test.select_test (0 ms)
[ RUN      ] queue_test.remove_test
[       OK ] queue_test.remove_test (2 ms)
[----------] 3 tests from queue_test (4 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (5 ms total)
[  PASSED  ] 3 tests.
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
exchange_table  queue_table   
sqlite> select * from queue_table;
queue1|1|0|0|k1=v1&
queue2|1|0|0|k1=v1&
queue4|1|0|0|k1=v1&
sqlite> 

新增和查询都没有问题,查表可以看到删除queue3也成功了

恢复测试:

恢复刚才的历史数据,并再次删除queue2

cpp 复制代码
#include "../mqserver/queue.hpp"
#include <gtest/gtest.h>

rabbitmq::MsgQueueManager::ptr mqmp;

class QueueTest : public testing::Environment 
{
    public:
        virtual void SetUp() override 
        {
            mqmp = std::make_shared<rabbitmq::MsgQueueManager>("./data/meta.db");
        }
        virtual void TearDown() override 
        {
            // mqmp->clear();
        }
};

// TEST(queue_test, insert_test) 
// {
//     google::protobuf::Map<std::string, std::string> map;
//     map["k1"] = "v1";
//     mqmp->declareQueue("queue1", true, false, false, map);
//     mqmp->declareQueue("queue2", true, false, false, map);
//     mqmp->declareQueue("queue3", true, false, false, map);
//     mqmp->declareQueue("queue4", true, false, false, map);
//     ASSERT_EQ(mqmp->size(), 4);
// }

TEST(queue_test, select_test) 
{
    ASSERT_EQ(mqmp->exists("queue1"), true);
    ASSERT_EQ(mqmp->exists("queue2"), true);
    ASSERT_EQ(mqmp->exists("queue3"), false);
    ASSERT_EQ(mqmp->exists("queue4"), true);

    rabbitmq::MsgQueue::ptr mqp = mqmp->selectQueue("queue2");
    ASSERT_NE(mqp.get(), nullptr);
    ASSERT_EQ(mqp->_name, "queue2");
    ASSERT_EQ(mqp->_durable, true);
    ASSERT_EQ(mqp->_exclusive, false);
    ASSERT_EQ(mqp->_auto_delete, false);
    ASSERT_EQ(mqp->getArgs(), std::string("k1=v1&"));
}

TEST(queue_test, remove_test) 
{
    mqmp->deleteQueue("queue2");
    ASSERT_EQ(mqmp->exists("queue2"), false);
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new QueueTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_queuetest.cpp -o mq_queuetest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_queuetest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from queue_test
[ RUN      ] queue_test.select_test
[       OK ] queue_test.select_test (0 ms)
[ RUN      ] queue_test.remove_test
[       OK ] queue_test.remove_test (2 ms)
[----------] 2 tests from queue_test (2 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (2 ms total)
[  PASSED  ] 2 tests.
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> select * from queue_table;
queue1|1|0|0|k1=v1&
queue4|1|0|0|k1=v1&
sqlite> 

4. 绑定信息(交换机-队列)管理

绑定信息,本质上就是一个交换机关联了哪些队列的描述。

  1. 定义绑定信息类
  • 交换机名称
  • 队列名称
  • binding_key(分发匹配规则-决定了哪些数据能被交换机放入队列)
  1. 定义绑定信息数据持久化类(数据持久化的 sqlite3 数据库中)
  • 创建/删除绑定信息数据表
  • 新增绑定信息数据
  • 移除指定绑定信息数据
  • 移除指定交换机相关绑定信息数据:移除交换机的时候会被调用
  • 移除指定队列相关绑定信息数据:移除队列的时候会被调用
  • 查询所有绑定信息数据:用于重启服务器时进行历史数据恢复
  1. 定义绑定信息数据管理类
  • 创建绑定信息,并添加管理(存在则 OK,不存在则创建)
  • 解除指定的绑定信息
  • 删除指定队列的所有绑定信息
  • 删除交换机相关的所有绑定信息
  • 获取交换机相关的所有绑定信息:交换机收到消息后,需要分发给自己关联的队列
  • 判断指定绑定信息是否存在
  • 获取当前绑定信息数量
  • 销毁所有绑定信息数据

4.1 绑定信息类

cpp 复制代码
	struct Binding
    {
        using ptr = std::shared_ptr<Binding>;
        std::string _exchange_name;
        std::string _msgqueue_name;
        std::string _binding_key;

        Binding() {}
        Binding(const std::string &ename, const std::string &qname, const std::string &key)
            :_exchange_name(ename), _msgqueue_name(qname), _binding_key(key)
        {}
    };

4.2 绑定信息数据持久化类

cpp 复制代码
	// 队列与绑定信息是一一对应的(因为是给某个交换机去绑定队列,因此一个交换机可能会有多个队列的绑定信息)
    // 因此先定义一个队列名,与绑定信息的映射关系,这个是为了方便通过队列名查找绑定信息
    using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
    // 然后定义一个交换机名称与队列绑定信息的映射关系,这个map中包含了所有的绑定信息,并且以交换机为单元进行了区分
    using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;

    // std::unordered_map<std::string, Binding::ptr>;  队列与绑定  
    // std::unordered_map<std::string, Binding::ptr>;  交换机与绑定
    // 采用上边两个结构,则删除交换机相关绑定信息的时候,不仅要删除交换机映射,还要删除对应队列中的映射,否则对象得不到释放
    class BindingMapper 
    {
    public:
        BindingMapper(const std::string &dbfile):_sql_helper(dbfile)
        {
            std::string path = FileHelper::parentDirectory(dbfile);
            FileHelper::createDirectory(path);
            _sql_helper.open();
            createTable();
        }
        // 创建绑定信息数据表 
        void createTable() 
        {
            // create table if not exists binding_table(exchange_name varchar(32), msgqueue_name, binding_key)
            std::stringstream sql;
            sql << "create table if not exists binding_table(";
            sql << "exchange_name varchar(32), ";
            sql << "msgqueue_name varchar(32), ";
            sql << "binding_key varchar(128));";
            assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
        }
        // 删除绑定信息数据表 
        void removeTable() 
        {
            std::string sql = "drop table if exists binding_table;";
            _sql_helper.exec(sql, nullptr, nullptr);
        }
        // 新增绑定信息数据 
        bool insert(Binding::ptr &binding) 
        {
            // insert into binding_table values('exchange1', 'msgqueue1', 'news.music.#');
            std::stringstream sql;
            sql << "insert into binding_table values(";
            sql << "'" << binding->_exchange_name << "', ";
            sql << "'" << binding->_msgqueue_name << "', ";
            sql << "'" << binding->_binding_key << "');";
            return _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        // 移除指定绑定信息数据 
        void remove(const std::string &ename, const std::string &qname) 
        {
            // delete from binding_table where exchange_name='' and msgqueue_name='';
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "exchange_name='" << ename << "' and ";
            sql << "msgqueue_name='" << qname << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        // 移除指定交换机相关绑定信息数据
        void removeExchangeBindings(const std::string &ename) 
        {
            // delete from binding_table where exchange_name='';
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "exchange_name='" << ename << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        // 移除指定队列相关绑定信息数据
        void removeMsgQueueBindings(const std::string &qname) 
        {
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "msgqueue_name='" << qname << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        // 查询所有绑定信息数据, 用于重启服务器时进行历史数据恢复
        BindingMap recovery() 
        {
            BindingMap result;
            // select exchange_name, msgqueue_name, binding_key from binding_table;
            std::string sql = "select exchange_name, msgqueue_name, binding_key from binding_table;";
            _sql_helper.exec(sql, selectCallback, &result);
            return result;
        }
    private:
        static int selectCallback(void* arg,int numcol,char** row,char** fields) 
        {
            BindingMap* result = (BindingMap*)arg;
            Binding::ptr bp = std::make_shared<Binding>(row[0], row[1], row[2]);
            // 为了防止 交换机相关的绑定信息已经存在,因此不能直接创建队列映射,进行添加,这样会覆盖历史数据
            // 因此得先获取交换机对应的映射对象,往里边添加数据
            // 但是,若这时候没有交换机对应的映射信息,因此这里的获取要使用引用(会保证不存在则自动创建)
            MsgQueueBindingMap &qmap = (*result)[bp->_exchange_name];
            qmap.insert(std::make_pair(bp->_msgqueue_name, bp));
            return 0;
        }
    private:
        SqliteHelper _sql_helper;
    };

4.3 绑定信息数据管理类

cpp 复制代码
	class BindingManager
    {
    public:
        using ptr = std::shared_ptr<BindingManager>;
        BindingManager(const std::string& dbfile):_mapper(dbfile)
        {
            _bindings = _mapper.recovery();
        }

        bool bind(const std::string &ename, const std::string &qname, const std::string &key, bool durable)
        {
            //加锁,构造一个队列的绑定信息对象, 添加映射关系
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _bindings.find(ename);
            if(it != _bindings.end() && it->second.find(qname) != it->second.end())
            {
                return true;
            }
            //绑定信息是否需要持久化,取决于什么? 交换机数据是持久化的,以及队列数据也是持久化的。
            Binding::ptr bp = std::make_shared<Binding>(ename, qname, key);
            if(durable)
            {
                bool ret = _mapper.insert(bp);
                if(ret == false) return false;
            }
            auto& qbmap = _bindings[ename];
            qbmap.insert(std::make_pair(qname, bp));
            return true;
        }
        void unBind(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end())
            {
                //没有交换机相关的绑定信息
                return;
            }
            auto qit = eit->second.find(qname);
            if(qit == eit->second.end())
            {
                //交换机没有队列相关的绑定信息
                return;
            }
            _mapper.remove(ename, qname);
            _bindings[ename].erase(qname);
        }
        void removeExchangeBindings(const std::string &ename)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeExchangeBindings(ename);
            _bindings.erase(ename);
        }
        void removeMsgQueueBindings(const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeMsgQueueBindings(qname);
            for(auto start = _bindings.begin(); start != _bindings.end(); ++start)
            {
                //遍历每个交换机的绑定信息,从中移除指定队列的相关信息
                start->second.erase(qname);
            }
        }
        MsgQueueBindingMap getExchangeBindings(const std::string &ename)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end())
            {
                //没有交换机相关的绑定信息
                return MsgQueueBindingMap();
            }
            return eit->second;
        }
        Binding::ptr getBinding(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end())
            {
                //没有交换机相关的绑定信息
                return Binding::ptr();
            }
            auto qit = eit->second.find(qname);
            if(qit == eit->second.end())
            {
                //交换机没有队列相关的绑定信息
                return Binding::ptr();
            }
            return qit->second;
        }
        bool exists(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end())
            {
                //没有交换机相关的绑定信息
                return false;
            }
            auto qit = eit->second.find(qname);
            if(qit == eit->second.end())
            {
                //交换机没有队列相关的绑定信息
                return false;
            }
            return true;
        }
        size_t size()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            size_t total_size = 0;
            for (auto start = _bindings.begin(); start != _bindings.end(); ++start) 
            {
                //遍历每个交换机的绑定信息
                total_size += start->second.size();
            }
            return total_size;
        }
        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeTable();
            _bindings.clear();
        }
    private:
        std::mutex _mutex;
        BindingMapper _mapper;
        BindingMap _bindings;
    };

4.4 测试

新增和查询测试:

cpp 复制代码
#include "../mqserver/binding.hpp"
#include <gtest/gtest.h>

rabbitmq::BindingManager::ptr bmp;

class QueueTest : public testing::Environment 
{
public:
    virtual void SetUp() override 
    {
        bmp = std::make_shared<rabbitmq::BindingManager>("./data/meta.db");
    }
    virtual void TearDown() override 
    {
        //bmp->clear();
    }
};

TEST(queue_test, insert_test) 
{
    bmp->bind("exchange1", "queue1", "news.music.#", true);
    bmp->bind("exchange1", "queue2", "news.sport.#", true);
    bmp->bind("exchange1", "queue3", "news.gossip.#", true);
    bmp->bind("exchange2", "queue1", "news.music.pop", true);
    bmp->bind("exchange2", "queue2", "news.sport.football", true);
    bmp->bind("exchange2", "queue3", "news.gossip.#", true);
    ASSERT_EQ(bmp->size(), 6);
}

TEST(queue_test, select_test) 
{
    ASSERT_EQ(bmp->exists("exchange1", "queue1"), true);
    ASSERT_EQ(bmp->exists("exchange1", "queue2"), true);
    ASSERT_EQ(bmp->exists("exchange1", "queue3"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue1"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);

    rabbitmq::Binding::ptr bp = bmp->getBinding("exchange1", "queue1");
    ASSERT_NE(bp.get(), nullptr);
    ASSERT_EQ(bp->_exchange_name, std::string("exchange1"));
    ASSERT_EQ(bp->_msgqueue_name, std::string("queue1"));
    ASSERT_EQ(bp->_binding_key, std::string("news.music.#"));
}

TEST(queue_test, select_exchange_test) 
{
    rabbitmq::MsgQueueBindingMap mqbm = bmp->getExchangeBindings("exchange1");
    ASSERT_EQ(mqbm.size(), 3);
    ASSERT_NE(mqbm.find("queue1"), mqbm.end());
    ASSERT_NE(mqbm.find("queue2"), mqbm.end());
    ASSERT_NE(mqbm.find("queue3"), mqbm.end());
}


int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new QueueTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_bindingtest.cpp -o mq_bindingtest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_bindingtest
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from queue_test
[ RUN      ] queue_test.insert_test
[       OK ] queue_test.insert_test (9 ms)
[ RUN      ] queue_test.select_test
[       OK ] queue_test.select_test (1 ms)
[ RUN      ] queue_test.select_exchange_test
[       OK ] queue_test.select_exchange_test (0 ms)
[----------] 3 tests from queue_test (10 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (14 ms total)
[  PASSED  ] 3 tests.
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
binding_table   exchange_table  queue_table   
sqlite> select * from binding_table;
exchange1|queue1|news.music.#
exchange1|queue2|news.sport.#
exchange1|queue3|news.gossip.#
exchange2|queue1|news.music.pop
exchange2|queue2|news.sport.football
exchange2|queue3|news.gossip.#
sqlite> 

删除测试:

先删除queue1,来断言测试exchange1和exchange2中还有没有queue1;再删除exchange1,断言测试exchange1原来绑定的队列还在不在;最后再解绑exchange2和queue2之间的绑定,断言测试

cpp 复制代码
#include "../mqserver/binding.hpp"
#include <gtest/gtest.h>

rabbitmq::BindingManager::ptr bmp;

class QueueTest : public testing::Environment 
{
public:
    virtual void SetUp() override 
    {
        bmp = std::make_shared<rabbitmq::BindingManager>("./data/meta.db");
    }
    virtual void TearDown() override 
    {
        //bmp->clear();
    }
};
TEST(queue_test, insert_test) 
{
    bmp->bind("exchange1", "queue1", "news.music.#", true);
    bmp->bind("exchange1", "queue2", "news.sport.#", true);
    bmp->bind("exchange1", "queue3", "news.gossip.#", true);
    bmp->bind("exchange2", "queue1", "news.music.pop", true);
    bmp->bind("exchange2", "queue2", "news.sport.football", true);
    bmp->bind("exchange2", "queue3", "news.gossip.#", true);
    ASSERT_EQ(bmp->size(), 6);
}

TEST(queue_test, select_test) 
{
    ASSERT_EQ(bmp->exists("exchange1", "queue1"), true);
    ASSERT_EQ(bmp->exists("exchange1", "queue2"), true);
    ASSERT_EQ(bmp->exists("exchange1", "queue3"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue1"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);

    rabbitmq::Binding::ptr bp = bmp->getBinding("exchange1", "queue1");
    ASSERT_NE(bp.get(), nullptr);
    ASSERT_EQ(bp->_exchange_name, std::string("exchange1"));
    ASSERT_EQ(bp->_msgqueue_name, std::string("queue1"));
    ASSERT_EQ(bp->_binding_key, std::string("news.music.#"));
}

TEST(queue_test, select_exchange_test) 
{
    rabbitmq::MsgQueueBindingMap mqbm = bmp->getExchangeBindings("exchange1");
    ASSERT_EQ(mqbm.size(), 3);
    ASSERT_NE(mqbm.find("queue1"), mqbm.end());
    ASSERT_NE(mqbm.find("queue2"), mqbm.end());
    ASSERT_NE(mqbm.find("queue3"), mqbm.end());
}

TEST(queue_test, remove_queue_test) 
{
    bmp->removeMsgQueueBindings("queue1");
    ASSERT_EQ(bmp->exists("exchange1", "queue1"), false);
    ASSERT_EQ(bmp->exists("exchange2", "queue1"), false);
}


TEST(queue_test, remove_exchange_test) 
{
    bmp->removeExchangeBindings("exchange1");
    ASSERT_EQ(bmp->exists("exchange1", "queue1"), false);
    ASSERT_EQ(bmp->exists("exchange1", "queue2"), false);
    ASSERT_EQ(bmp->exists("exchange1", "queue3"), false);
}

TEST(queue_test, remove_single_test) 
{
    ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);
    bmp->unBind("exchange2", "queue2");
    ASSERT_EQ(bmp->exists("exchange2", "queue2"), false);
    ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);
}




int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new QueueTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_bindingtest.cpp -o mq_bindingtest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_bindingtest
[==========] Running 6 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 6 tests from queue_test
[ RUN      ] queue_test.insert_test
[       OK ] queue_test.insert_test (8 ms)
[ RUN      ] queue_test.select_test
[       OK ] queue_test.select_test (0 ms)
[ RUN      ] queue_test.select_exchange_test
[       OK ] queue_test.select_exchange_test (0 ms)
[ RUN      ] queue_test.remove_queue_test
[       OK ] queue_test.remove_queue_test (2 ms)
[ RUN      ] queue_test.remove_exchange_test
[       OK ] queue_test.remove_exchange_test (2 ms)
[ RUN      ] queue_test.remove_single_test
[       OK ] queue_test.remove_single_test (1 ms)
[----------] 6 tests from queue_test (13 ms total)

[----------] Global test environment tear-down
[==========] 6 tests from 1 test suite ran. (13 ms total)
[  PASSED  ] 6 tests.
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> select * from binding_table;
exchange2|queue3|news.gossip.#
sqlite> .exit

可以看到最后表中只剩下exchange2和queue3之间的绑定信息

恢复历史数据测试:

我们在恢复历史数据时,又将刚刚测试的所有交换机和队列新增绑定

cpp 复制代码
#include "../mqserver/binding.hpp"
#include <gtest/gtest.h>

rabbitmq::BindingManager::ptr bmp;

class QueueTest : public testing::Environment 
{
public:
    virtual void SetUp() override 
    {
        bmp = std::make_shared<rabbitmq::BindingManager>("./data/meta.db");
    }
    virtual void TearDown() override 
    {
        //bmp->clear();
    }
};
TEST(queue_test, insert_test) 
{
    bmp->bind("exchange1", "queue1", "news.music.#", true);
    bmp->bind("exchange1", "queue2", "news.sport.#", true);
    bmp->bind("exchange1", "queue3", "news.gossip.#", true);
    bmp->bind("exchange2", "queue1", "news.music.pop", true);
    bmp->bind("exchange2", "queue2", "news.sport.football", true);
    bmp->bind("exchange2", "queue3", "news.gossip.#", true);
    ASSERT_EQ(bmp->size(), 6);
}

TEST(queue_test, recovery_test) 
{
    ASSERT_EQ(bmp->exists("exchange1", "queue1"), true);
    ASSERT_EQ(bmp->exists("exchange1", "queue2"), true);
    ASSERT_EQ(bmp->exists("exchange1", "queue3"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue1"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);
    ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);
}

// TEST(queue_test, select_test) 
// {
//     ASSERT_EQ(bmp->exists("exchange1", "queue1"), true);
//     ASSERT_EQ(bmp->exists("exchange1", "queue2"), true);
//     ASSERT_EQ(bmp->exists("exchange1", "queue3"), true);
//     ASSERT_EQ(bmp->exists("exchange2", "queue1"), true);
//     ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);
//     ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);

//     rabbitmq::Binding::ptr bp = bmp->getBinding("exchange1", "queue1");
//     ASSERT_NE(bp.get(), nullptr);
//     ASSERT_EQ(bp->_exchange_name, std::string("exchange1"));
//     ASSERT_EQ(bp->_msgqueue_name, std::string("queue1"));
//     ASSERT_EQ(bp->_binding_key, std::string("news.music.#"));
// }

// TEST(queue_test, select_exchange_test) 
// {
//     rabbitmq::MsgQueueBindingMap mqbm = bmp->getExchangeBindings("exchange1");
//     ASSERT_EQ(mqbm.size(), 3);
//     ASSERT_NE(mqbm.find("queue1"), mqbm.end());
//     ASSERT_NE(mqbm.find("queue2"), mqbm.end());
//     ASSERT_NE(mqbm.find("queue3"), mqbm.end());
// }

// TEST(queue_test, remove_queue_test) 
// {
//     bmp->removeMsgQueueBindings("queue1");
//     ASSERT_EQ(bmp->exists("exchange1", "queue1"), false);
//     ASSERT_EQ(bmp->exists("exchange2", "queue1"), false);
// }


// TEST(queue_test, remove_exchange_test) 
// {
//     bmp->removeExchangeBindings("exchange1");
//     ASSERT_EQ(bmp->exists("exchange1", "queue1"), false);
//     ASSERT_EQ(bmp->exists("exchange1", "queue2"), false);
//     ASSERT_EQ(bmp->exists("exchange1", "queue3"), false);
// }

// TEST(queue_test, remove_single_test) 
// {
//     ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);
//     bmp->unBind("exchange2", "queue2");
//     ASSERT_EQ(bmp->exists("exchange2", "queue2"), false);
//     ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);
// }




int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new QueueTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_bindingtest.cpp -o mq_bindingtest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_bindingtest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from queue_test
[ RUN      ] queue_test.insert_test
[       OK ] queue_test.insert_test (8 ms)
[ RUN      ] queue_test.recovery_test
[       OK ] queue_test.recovery_test (0 ms)
[----------] 2 tests from queue_test (8 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (9 ms total)
[  PASSED  ] 2 tests.
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ sqlite3 data/meta.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> select * from binding_table;
exchange2|queue3|news.gossip.#
exchange1|queue1|news.music.#
exchange1|queue2|news.sport.#
exchange1|queue3|news.gossip.#
exchange2|queue1|news.music.pop
exchange2|queue2|news.sport.football
sqlite> .exit

可以看到表中最开始的第一个是历史数据,之后都是新增的,原来的历史数据已经存在,就不会重复绑定


5. 队列消息管理

因为消息数据需要在网络中进行传输,因此消息的类型定义使用 protobuf 进行,因为 protobuf 中自带了序列化和反序列化功能,因此操作起来会简便一些。需要特别说明的是,消息的存储并没有使用数据库,因为消息长度通常不定,且有些消息可能会非常庞大,因此并不适合存储在数据库中,因此我们的处理方式(包括 RabbitMQ)是直接将消息存储在文件中进行管理,而内存中管理的消息只需要记录好自己在文件中的所在位置和长度即可。

为了便于管理,消息管理以队列为单元进行管理,因此每个队列都会有自己独立的数据存储文件。

  1. 创建消息类型的 proto 文件,并使用 protobuf 命令生成相对应的代码文件。
  • 属性:消息 ID, 路由主题,持久化模式标志
  • 消息内容
  • 有效标志(持久化需要)
  • 持久化位置(内存中)
  • 持久化消息长度(内存中)
  1. 消息的持久化管理
  • 管理数据
    • 队列消息文件存储的路径
    • 队列消息的存储文件名
    • 队列消息的临时交换文件名
  • 管理操作
    • 日志消息存储在文件中(4B 长度+(属性+内容+有效位)序列化消息,连续存储即可)
    • 提供队列消息文件创建/删除功能
    • 提供队列消息的新增持久化/删除持久化
    • 提供持久化内容的垃圾回收(其实就是重新加载出所有有效消息返回,并重新生成新的消息存储文件)
  1. 消息的管理(以队列为单位进行管理)
  • 队列消息管理数据
    • 队列名称
    • 待推送消息链表
    • 持久化消息 hash
    • 待确认消息 hash
    • 有效消息数量
    • 已经持久化消息总量
    • 持久化管理句柄
  • 队列管理操作
    • 新增消息
    • 获取队首消息(获取的同时将消息加入待确认队列)
    • 移除指定待确认消息
    • 获取队列待消费&待确认消息数量
    • 恢复队列历史消息。
    • 销毁队列所有消息
    • 判断队列消息是否为空
  • 消息的总体对外管理
    • 初始化新建队列的消息管理结构,并创建消息存储文件
    • 删除队列的消息管理结构,以及消息存储文件
    • 向指定队列新增消息
    • 获取指定队列队首消息
    • 确认指定队列待确认消息(删除)
    • 判断指定队列消息是否为空

创建消息类型的 proto 文件,并使用 protobuf 命令生成相对应的代码文件。这一步我们在一开始的消息类型定义的时候就已经做完了

5.1 消息的持久化管理

cpp 复制代码
	#define DATAFILE_SUBFIX ".mqd"
    #define TMPFILE_SUBFIX ".mqd.tmp"
    using MessagePtr = std::shared_ptr<rabbitmq::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) 
            {
                assert(FileHelper::createDirectory(basedir));
            }
            createMsgFile();
        }

        // 队列消息文件的创建
        bool createMsgFile()
        {
            if (FileHelper(_datafile).exists() == true)
            {
                return true;
            }
            bool ret = FileHelper::createFile(_datafile);
            if(ret == false)
            {
                DLOG("创建队列数据文件 %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->payload().SerializeAsString();
            if (body.size() != msg->length()) 
            {
                DLOG("不能修改文件中的数据信息,因为新生成的数据与原数据长度不一致!");
                return false;
            }
            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;
            //1. 加载出文件中所有的有效数据;  存储格式 4字节长度|数据|4字节长度|数据.....
            ret = load(result);
            if(ret == false)
            {
                DLOG("加载有效数据失败!\n");
                return result;
            }
            //2. 将有效数据,进行序列化存储到临时文件中
            FileHelper::createFile(_tmpfile);
            for(auto& msg : result)
            {
                DLOG("向临时文件写入数据: %s", msg->payload().body().c_str());
                ret = insert(_tmpfile, msg);
                if(ret == false)
                {
                    DLOG("向临时文件写入消息数据失败!!");
                    return result;
                }
            }
            //3. 删除源文件
            ret = FileHelper::removeFile(_datafile);
            if(ret == false)
            {
                DLOG("删除源文件失败!");
                return result;
            }
            //4. 修改临时文件名,为源文件名称
            ret = FileHelper(_tmpfile).rename(_datafile);
            if(ret == false)
            {
                DLOG("修改临时文件名称失败!");
                return result;
            }
            //5. 返回新的有效数据
            return result;
        }
    private:
        bool load(std::list<MessagePtr>& result)
        {
            // 加载出文件中所有的有效数据;  存储格式 4字节长度|数据|4字节长度|数据.....
            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 == false)
                {
                    DLOG("读取消息长度失败!");
                    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 == false)
                {
                    DLOG("读取消息数据失败!");
                    return false;
                }
                offset += msg_size;
                MessagePtr msgp = std::make_shared<Message>();
                // 将加载出来的消息进行反序列化
                msgp->mutable_payload()->ParseFromString(msg_body);
                // 如果是无效消息,则直接处理下一个
                if (msgp->payload().valid() == "0")  
                {
                    DLOG("加载到无效消息:%s", msgp->payload().body().c_str());
                    continue;
                }
                // 有效消息则保存起来
                result.push_back(msgp);
            }
            return true;
        }
        bool insert(const std::string& filename, MessagePtr& msg)
        {
            //新增数据都是添加在文件末尾的
            //1. 进行消息的序列化,获取到格式化后的消息
            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;
            }
            //3. 将数据写入文件的指定位置
            ret = helper.write(body.c_str(), fsize + sizeof(size_t), msg_size);
            if(ret == false)
            {
                DLOG("向队列数据文件写入数据失败!");
                return false;
            }
            //4. 更新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;
    };

这个类主要用于:

  • 将消息队列中的消息持久化到磁盘文件
  • 支持消息的插入、标记删除和垃圾回收
  • 在队列异常恢复时能够重新加载未处理的消息

5.1.1 核心流程详解

  1. 初始化流程
  • 构造函数接收基础目录和队列名
  • 自动确保目录存在(不存在则创建)
  • 构造数据文件和临时文件的完整路径
  • 创建或验证消息文件
  1. 数据存储格式

文件存储采用二进制格式:

plain 复制代码
[4字节长度] + [消息体] + [4字节长度] + [消息体] + ...

其中:

  • 每个消息的前4字节存储消息体长度(size_t类型)
  • 消息体是protobuf序列化后的二进制数据
  1. 插入消息流程(insert方法)
plain 复制代码
1. 将消息对象序列化为二进制字符串
2. 获取当前文件大小,确定写入位置
3. 写入4字节的消息长度
4. 写入序列化后的消息体
5. 更新消息对象的offset和length信息
  • 新消息总是追加到文件末尾
  • 记录消息在文件中的偏移和长度,便于后续操作
  1. 删除消息流程(remove方法)
plain 复制代码
1. 不实际删除数据,而是将消息的valid字段标记为"0"
2. 将修改后的消息序列化
3. 根据消息的offset定位到文件中该消息的位置
4. 重新写入修改后的消息(覆盖原数据)
  • 这是逻辑删除而非物理删除
  • 通过标记位标识消息已处理/无效
  1. 垃圾回收流程(gc方法)
plain 复制代码
1. 加载所有有效消息(valid != "0")
2. 创建临时文件
3. 将所有有效消息按顺序写入临时文件
4. 删除原数据文件
5. 将临时文件重命名为原文件名
6. 返回所有有效消息的列表
  • 实际清理标记为无效的消息
  • 避免文件不断增大
  • 在清理过程中保持数据一致性
  1. 加载消息流程(load方法)
plain 复制代码
1. 打开数据文件
2. 循环读取:
   a. 先读取4字节消息长度
   b. 根据长度读取消息体
   c. 反序列化为消息对象
   d. 检查valid字段,只保留有效消息
3. 返回有效消息列表

5.1.2 文件管理机制

  1. 双文件策略
  • .mqd文件:主数据文件,存储所有消息
  • .mqd.tmp文件:临时文件,用于垃圾回收时的安全操作
  1. 安全写入机制
  • 通过FileHelper类进行文件操作
  • 垃圾回收时先写入临时文件,成功后再替换原文件
  • 避免在写入过程中发生异常导致数据损坏

5.2 消息的管理(以队列为单位进行管理)

5.2.1 队列消息管理

cpp 复制代码
	class QueueMessage
    {
    public:
        using ptr = std::shared_ptr<QueueMessage>;
        QueueMessage(std::string &basedir, const std::string &qname)
            :_qname(qname), _mapper(basedir, qname), _valid_count(0), _total_count(0)
        {} 
        // 恢复队列历史消息
        bool recovery()
        {
            _waitpush_msgs = _mapper.gc();
            for(auto& msg : _waitpush_msgs)
            {
                _durable_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
            }
            _valid_count = _total_count = _waitpush_msgs.size();
            return true;
        }
        // 新增消息
        bool insert(const BasicProperties *bp, const std::string &body, bool queue_is_durable)
        {
            //1. 构造消息对象
            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_routing_key(bp->routing_key());
            }
            else
            {
                DeliveryMode mode = queue_is_durable ? DeliveryMode::DURABLE : DeliveryMode::UNDURABLE;
                msg->mutable_payload()->mutable_properties()->set_id(UUIDHelper::uuid());
                msg->mutable_payload()->mutable_properties()->set_delivery_mode(mode);
                msg->mutable_payload()->mutable_properties()->set_routing_key("");
            }
            // 上面对象构造是局部数据,可以不用加锁
            std::unique_lock<std::mutex> lock(_mutex);
            //2. 判断消息是否需要持久化
            if(msg->payload().properties().delivery_mode() == DeliveryMode::DURABLE)
            {
                msg->mutable_payload()->set_valid("1");//在持久化存储中表示数据有效
                //3. 进行持久化存储
                bool ret = _mapper.insert(msg);
                if(ret == false)
                {
                    DLOG("持久化存储消息:%s 失败了!", body.c_str());
                    return false;
                }
                _valid_count += 1;//持久化信息中的数据量+1
                _total_count += 1;
                _durable_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
            }
            //4. 内存的管理
            _waitpush_msgs.push_back(msg);
            return true;
        }
        // 获取队首消息(获取的同时将消息加入待确认队列)
        MessagePtr front()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_waitpush_msgs.size() == 0) 
            {
                return MessagePtr();
            }
            //获取一条队首消息:从_msgs中取出数据
            MessagePtr msg = _waitpush_msgs.front();
            _waitpush_msgs.pop_front();
            //将该消息对象,向待确认的hash表中添加一份,等到收到消息确认后进行删除
            _waitack_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
            return msg;
        }
        
        // 移除指定待确认消息
        bool remove(const std::string &msg_id)
        {
            // 每次删除消息后,判断是否需要垃圾回收
            std::unique_lock<std::mutex> lock(_mutex);
            //1. 从待确认hashmap中查找消息
            auto it = _waitack_msgs.find(msg_id);
            if(it == _waitack_msgs.end())
            {
                DLOG("没有找到要删除的消息:%s!", msg_id.c_str());
                return true;
            }
            //2. 根据消息的持久化模式,决定是否删除持久化信息
            if(it->second->payload().properties().delivery_mode() == DeliveryMode::DURABLE)
            {
                //3. 删除持久化信息
                _mapper.remove(it->second);
                _durable_msgs.erase(msg_id);
                _valid_count -= 1; //持久化文件中有效消息数量 -1
                gc(); //内部判断是否需要垃圾回收,需要的话则回收一下
            }
            //4. 删除内存中的信息
            _waitack_msgs.erase(msg_id);
            return true;
        }

        //获取队列待消费消息数量
        size_t getable_count()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _waitpush_msgs.size();
        }
        // 获取队列中消息总数量
        size_t total_count()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _total_count;
        }
        // 获取队列中有效消息数量
        size_t durable_count()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _durable_msgs.size();
        }
        //获取队列待确认消息数量
        size_t waitack_count()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _waitack_msgs.size();
        }
        // 销毁队列所有消息
        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeMsgFile();
            _waitpush_msgs.clear();
            _durable_msgs.clear();
            _waitack_msgs.clear();
            _valid_count = 0;
            _total_count = 0;
        }
    private:
        bool GcCheck()
        {
            //持久化的消息总量大于2000, 且其中有效比例低于50%则需要持久化
            if(_total_count > 2000 && _valid_count * 10 / _total_count < 5)
            {
                return true;
            }
            return false;
        }

        void gc()
        {
            if(GcCheck() == false) return;
            //1. 进行垃圾回收,获取到垃圾回收后,有效的消息信息链表
            std::list<MessagePtr> msgs = _mapper.gc();
            for(auto& msg : msgs)
            {
                auto it = _durable_msgs.find(msg->payload().properties().id());
                if (it == _durable_msgs.end()) 
                {
                    DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
                    _waitpush_msgs.push_back(msg); ///做法:重新添加到推送链表的末尾
                    _durable_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
                    continue;
                }
                //2. 更新每一条消息的实际存储位置
                it->second->set_offset(msg->offset());
                it->second->set_length(msg->length());
            }
            //3. 更新当前的有效消息数量 & 总的持久化消息数量
            _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> _waitpush_msgs; // 待推送消息
        std::unordered_map<std::string, MessagePtr> _durable_msgs; // 持久化消息hash
        std::unordered_map<std::string, MessagePtr> _waitack_msgs; // 待确认消息hash
    };

这个类实现了消息队列的核心管理功能:

  1. 消息持久化:将消息保存到磁盘,确保系统重启后不丢失
  2. 内存缓存:在内存中维护消息的多种状态
  3. 消费确认机制:支持消息的可靠消费
  4. 垃圾回收:自动清理无效消息,优化存储空间
  5. 消息恢复:系统重启后从磁盘恢复未处理的消息

核心流程详解

  1. 系统恢复(recovery)
plain 复制代码
启动时执行:
1. 调用_mapper.gc() 加载磁盘中所有有效消息
2. 将有效消息放入_waitpush_msgs等待重新处理
3. 在_durable_msgs中建立消息ID索引
4. 更新计数器
  • 确保系统异常重启后消息不丢失
  • 自动清理磁盘中的无效数据
  1. 消息插入(insert)
plain 复制代码
步骤:
1. 构造消息对象,设置属性(ID、路由键、持久化模式等)
2. 如果是持久化消息:
   a. 设置valid标志为"1"
   b. 调用_mapper.insert()写入磁盘
   c. 更新计数器
   d. 添加到_durable_msgs索引
3. 将消息加入_waitpush_msgs队列尾部
  • 支持持久化(DURABLE)和非持久化(UNDURABLE)两种模式
  • 持久化消息同时写入内存和磁盘
  • 非持久化消息只存储在内存中
  1. 消息消费(front + remove)
plain 复制代码
front()流程:
1. 从_waitpush_msgs取出队首消息
2. 将该消息移入_waitack_msgs等待确认
3. 返回消息给消费者

remove()流程:
1. 在_waitack_msgs中查找消息
2. 如果是持久化消息:
   a. 调用_mapper.remove()标记为逻辑删除
   b. 从_durable_msgs删除索引
   c. 更新计数器
   d. 检查是否需要垃圾回收
3. 从_waitack_msgs删除消息
  • 实现了可靠的消费确认机制
  • 只有消费者明确确认后才真正删除消息
  • 支持消息重发(如果消费者未确认,消息仍在_waitack_msgs中)
  1. 垃圾回收(gc)
plain 复制代码
触发条件(GcCheck):
1. 持久化消息总数 > 2000
2. 有效消息比例 < 50%

gc()流程:
1. 调用_mapper.gc()进行磁盘整理
2. 遍历返回的有效消息列表:
   a. 更新内存中消息的存储位置(offset/length)
   b. 如果消息不在_durable_msgs中,重新加入队列
3. 更新计数器
  • 避免磁盘文件无限增长
  • 提高读取效率
  • 自动维护数据一致性

消息状态流转

plain 复制代码
新增 → 待推送(_waitpush_msgs) → 已发送(_waitack_msgs) → 已确认(删除)
           ↑
    系统恢复时重新加载

三种存储位置

  • 内存队列:_waitpush_msgs(等待消费)
  • 内存哈希表:_durable_msgs + _waitack_msgs(快速查找)
  • 磁盘文件:通过MessageMapper持久化

关键特性

  1. 线程安全
  • 所有公共方法都使用std::unique_lock<std::mutex>保护
  • 支持多线程并发访问
  1. 消息可靠性
  • 持久化消息确保不丢失
  • 消费确认机制防止消息误删
  • 系统恢复时自动重建消息队列
  1. 性能优化
  • 内存中使用哈希表(O(1)查找)和链表(O(1)插入/删除)
  • 磁盘使用顺序读写,批量垃圾回收
  • 智能垃圾回收策略,避免频繁整理
  1. 容错处理
cpp 复制代码
// 垃圾回收后消息一致性检查
if (it == _durable_msgs.end()) 
{
    DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
    _waitpush_msgs.push_back(msg);  // 重新加入队列
    _durable_msgs.insert(...);       // 重建索引
    continue;
}
  • 处理内存和磁盘数据不一致的情况
  • 自动修复数据,避免消息丢失

5.2.2 消息的总体对外管理

cpp 复制代码
	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);
                auto it = _queue_msgs.find(qname);
                if(it != _queue_msgs.end())
                    return;
                qmp = std::make_shared<QueueMessage>(_basedir, qname);
                _queue_msgs.insert(std::make_pair(qname, qmp));
            }
            // 历史数据的恢复有自己的锁保护,不需要再通过上面的锁保护
            // 否则容易造成锁冲突
            qmp->recovery();
        }

        // 删除队列的消息管理结构,以及消息存储文件
        void destroyQueueMessage(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 queue_is_durable)
        {
            QueueMessage::ptr qmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _queue_msgs.find(qname);
                if(it != _queue_msgs.end())
                {
                    DLOG("向队列%s新增消息失败: 没有找到消息管理句柄!", qname.c_str());
                    return false;
                }
                qmp = it->second;
            }
            return qmp->insert(bp, body, queue_is_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()) 
                {
                    DLOG("获取队列%s队首消息失败: 没有找到消息管理句柄!", qname.c_str());
                    return MessagePtr();
                }
                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()) 
                {
                    DLOG("确认队列%s消息%s失败: 没有找到消息管理句柄!", qname.c_str(), msg_id.c_str());
                    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()) 
                {
                    DLOG("获取队列%s待推送消息数量失败: 没有找到消息管理句柄!", qname.c_str());
                    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()) 
                {
                    DLOG("获取队列%s总持久化消息数量失败: 没有找到消息管理句柄!", qname.c_str());
                    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()) 
                {
                    DLOG("获取队列%s有效持久化消息数量失败: 没有找到消息管理句柄!", qname.c_str());
                    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()) 
                {
                    DLOG("获取队列%s待确认消息数量失败: 没有找到消息管理句柄!", qname.c_str());
                    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;
    };

这个类是一个多队列管理器,主要负责:

  1. 管理多个队列:支持创建、销毁、操作多个消息队列
  2. 提供统一接口:为外部系统提供统一的消息操作API
  3. 生命周期管理:管理队列的创建、初始化、销毁全过程
  4. 线程安全:确保多线程环境下的并发安全

核心数据结构:

cpp 复制代码
std::unordered_map<std::string, QueueMessage::ptr> _queue_msgs;
  • 键:队列名称(qname)
  • 值:QueueMessage对象的智能指针
  • 通过队列名快速查找对应的队列管理器

核心流程详解

  1. 初始化队列(initQueueMessage)
plain 复制代码
流程:
1. 加锁保护哈希表
2. 检查队列是否已存在(避免重复初始化)
3. 创建QueueMessage对象
4. 将队列添加到管理哈希表
5. 释放锁
6. 调用recovery()恢复队列历史消息
  • 延迟恢复:在锁外执行recovery,避免锁冲突
  • 幂等性:已存在的队列不再重复初始化
  • 分离职责:初始化与恢复分离
  1. 销毁队列(destroyQueueMessage)
plain 复制代码
流程:
1. 加锁保护哈希表
2. 查找队列,如果存在则获取指针并从哈希表移除
3. 释放锁
4. 调用clear()清理队列所有消息和文件
  • 原子操作:从哈希表中移除是原子操作
  • 安全清理:在锁外执行清理,避免死锁
  • 资源释放:彻底清理队列的所有资源
  1. 插入消息(insert)
plain 复制代码
流程:
1. 加锁查找队列
2. 如果队列不存在,记录日志并返回失败
3. 获取队列指针
4. 释放锁
5. 调用队列的insert方法
  • 快速失败:队列不存在立即返回
  • 最小锁范围:只在查找队列时加锁
  1. 获取消息(front)
plain 复制代码
流程:
1. 加锁查找队列
2. 如果队列不存在,记录日志并返回空指针
3. 获取队列指针
4. 释放锁
5. 调用队列的front方法
  • 非阻塞获取:队列为空时返回空指针
  • 消息流转:内部会移动消息到待确认状态
  1. 确认消息(ack)
plain 复制代码
流程:
1. 加锁查找队列
2. 如果队列不存在,记录日志
3. 获取队列指针
4. 释放锁
5. 调用队列的remove方法
  • 可靠确认:只有确认的消息才会真正删除
  • 异步清理:可能触发垃圾回收

5.3 测试

新增消息测试:

cpp 复制代码
#include "../mqserver/message.hpp"
#include <gtest/gtest.h>

rabbitmq::MessageManager::ptr mmp;

class MessageTest : public testing::Environment 
{
public:
    virtual void SetUp() override 
    {
        mmp = std::make_shared<rabbitmq::MessageManager>("./data/message/");
        mmp->initQueueMessage("queue1");
    }
    virtual void TearDown() override 
    {
        //mmp->clear();
    }
};

//新增消息测试:新增消息,然后观察可获取消息数量,以及持久化消息数量
TEST(message_test, insert_test) 
{
    rabbitmq::BasicProperties properties;
    properties.set_id(rabbitmq::UUIDHelper::uuid());
    properties.set_delivery_mode(rabbitmq::DeliveryMode::DURABLE);
    properties.set_routing_key("news.music.pop");
    mmp->insert("queue1", &properties, "Hello World-1", true);
    mmp->insert("queue1", nullptr, "Hello World-2", true);
    mmp->insert("queue1", nullptr, "Hello World-3", true);
    mmp->insert("queue1", nullptr, "Hello World-4", true);
    mmp->insert("queue1", nullptr, "Hello World-5", false);
    ASSERT_EQ(mmp->getable_count("queue1"), 5);
    ASSERT_EQ(mmp->total_count("queue1"), 4);
    ASSERT_EQ(mmp->durable_count("queue1"), 4);
    ASSERT_EQ(mmp->waitack_count("queue1"), 0);
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new MessageTest);
    return RUN_ALL_TESTS();
}

注意:这里待确认消息数量为0是因为我们压根就没有推送给客户端,说白了就是我们这里还没有客户端来消费数据,只有推送给客户端,却还没收到客户端消息确认的消息才会存入待确认hashmap中

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_msgtest.cpp ../mqcommon/msg.pb.cc -o mq_msgtest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_msgtest
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from message_test
[ RUN      ] message_test.insert_test
[       OK ] message_test.insert_test (0 ms)
[----------] 1 test from message_test (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

获取消息测试:

cpp 复制代码
#include "../mqserver/message.hpp"
#include <gtest/gtest.h>

rabbitmq::MessageManager::ptr mmp;

class MessageTest : public testing::Environment 
{
public:
    virtual void SetUp() override 
    {
        mmp = std::make_shared<rabbitmq::MessageManager>("./data/message/");
        mmp->initQueueMessage("queue1");
    }
    virtual void TearDown() override 
    {
        //mmp->clear();
    }
};

//新增消息测试:新增消息,然后观察可获取消息数量,以及持久化消息数量
TEST(message_test, insert_test) 
{
    rabbitmq::BasicProperties properties;
    properties.set_id(rabbitmq::UUIDHelper::uuid());
    properties.set_delivery_mode(rabbitmq::DeliveryMode::DURABLE);
    properties.set_routing_key("news.music.pop");
    mmp->insert("queue1", &properties, "Hello World-1", true);
    mmp->insert("queue1", nullptr, "Hello World-2", true);
    mmp->insert("queue1", nullptr, "Hello World-3", true);
    mmp->insert("queue1", nullptr, "Hello World-4", true);
    mmp->insert("queue1", nullptr, "Hello World-5", false);
    ASSERT_EQ(mmp->getable_count("queue1"), 5);
    ASSERT_EQ(mmp->total_count("queue1"), 4);
    ASSERT_EQ(mmp->durable_count("queue1"), 4);
    ASSERT_EQ(mmp->waitack_count("queue1"), 0);
}


//获取消息测试:获取一条消息,然后在不进行确认的情况下,查看可获取消息数量,待确认消息数量,以及测试消息获取的顺序
TEST(message_test, select_test) 
{
    ASSERT_EQ(mmp->getable_count("queue1"), 5);
    rabbitmq::MessagePtr msg1 = mmp->front("queue1");
    ASSERT_NE(msg1.get(), nullptr);
    ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
    ASSERT_EQ(mmp->getable_count("queue1"), 4);
    ASSERT_EQ(mmp->waitack_count("queue1"), 1);

    rabbitmq::MessagePtr msg2 = mmp->front("queue1");
    ASSERT_NE(msg2.get(), nullptr);
    ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
    ASSERT_EQ(mmp->getable_count("queue1"), 3);
    ASSERT_EQ(mmp->waitack_count("queue1"), 2);

    rabbitmq::MessagePtr msg3 = mmp->front("queue1");
    ASSERT_NE(msg3.get(), nullptr);
    ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
    ASSERT_EQ(mmp->getable_count("queue1"), 2);
    ASSERT_EQ(mmp->waitack_count("queue1"), 3);

    rabbitmq::MessagePtr msg4 = mmp->front("queue1");
    ASSERT_NE(msg4.get(), nullptr);
    ASSERT_EQ(msg4->payload().body(), std::string("Hello World-4"));
    ASSERT_EQ(mmp->getable_count("queue1"), 1);
    ASSERT_EQ(mmp->waitack_count("queue1"), 4);

    rabbitmq::MessagePtr msg5 = mmp->front("queue1");
    ASSERT_NE(msg5.get(), nullptr);
    ASSERT_EQ(msg5->payload().body(), std::string("Hello World-5"));
    ASSERT_EQ(mmp->getable_count("queue1"), 0);
    ASSERT_EQ(mmp->waitack_count("queue1"), 5);
}

int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new MessageTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_msgtest.cpp ../mqcommon/msg.pb.cc -o mq_msgtest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_msgtest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from message_test
[ RUN      ] message_test.insert_test
[       OK ] message_test.insert_test (1 ms)
[ RUN      ] message_test.select_test
[       OK ] message_test.select_test (0 ms)
[----------] 2 tests from message_test (1 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (1 ms total)
[  PASSED  ] 2 tests.

恢复历史消息测试:

cpp 复制代码
#include "../mqserver/message.hpp"
#include <gtest/gtest.h>

rabbitmq::MessageManager::ptr mmp;

class MessageTest : public testing::Environment 
{
public:
    virtual void SetUp() override 
    {
        mmp = std::make_shared<rabbitmq::MessageManager>("./data/message/");
        mmp->initQueueMessage("queue1");
    }
    virtual void TearDown() override 
    {
        //mmp->clear();
    }
};

//新增消息测试:新增消息,然后观察可获取消息数量,以及持久化消息数量
// TEST(message_test, insert_test) 
// {
//     rabbitmq::BasicProperties properties;
//     properties.set_id(rabbitmq::UUIDHelper::uuid());
//     properties.set_delivery_mode(rabbitmq::DeliveryMode::DURABLE);
//     properties.set_routing_key("news.music.pop");
//     mmp->insert("queue1", &properties, "Hello World-1", true);
//     mmp->insert("queue1", nullptr, "Hello World-2", true);
//     mmp->insert("queue1", nullptr, "Hello World-3", true);
//     mmp->insert("queue1", nullptr, "Hello World-4", true);
//     mmp->insert("queue1", nullptr, "Hello World-5", false);
//     ASSERT_EQ(mmp->getable_count("queue1"), 5);
//     ASSERT_EQ(mmp->total_count("queue1"), 4);
//     ASSERT_EQ(mmp->durable_count("queue1"), 4);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 0);
// }


//获取消息测试:获取一条消息,然后在不进行确认的情况下,查看可获取消息数量,待确认消息数量,以及测试消息获取的顺序
// TEST(message_test, select_test) 
// {
//     ASSERT_EQ(mmp->getable_count("queue1"), 5);
//     rabbitmq::MessagePtr msg1 = mmp->front("queue1");
//     ASSERT_NE(msg1.get(), nullptr);
//     ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 4);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 1);

//     rabbitmq::MessagePtr msg2 = mmp->front("queue1");
//     ASSERT_NE(msg2.get(), nullptr);
//     ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 3);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 2);

//     rabbitmq::MessagePtr msg3 = mmp->front("queue1");
//     ASSERT_NE(msg3.get(), nullptr);
//     ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 2);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 3);

//     rabbitmq::MessagePtr msg4 = mmp->front("queue1");
//     ASSERT_NE(msg4.get(), nullptr);
//     ASSERT_EQ(msg4->payload().body(), std::string("Hello World-4"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 1);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 4);

//     rabbitmq::MessagePtr msg5 = mmp->front("queue1");
//     ASSERT_NE(msg5.get(), nullptr);
//     ASSERT_EQ(msg5->payload().body(), std::string("Hello World-5"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 0);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 5);
// }


//恢复历史数据测试
TEST(message_test2, recovery_test) 
{
    ASSERT_EQ(mmp->getable_count("queue1"), 4);
    rabbitmq::MessagePtr msg1 = mmp->front("queue1");
    ASSERT_NE(msg1.get(), nullptr);
    ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
    ASSERT_EQ(mmp->getable_count("queue1"), 3);
    ASSERT_EQ(mmp->waitack_count("queue1"), 1);

    rabbitmq::MessagePtr msg2 = mmp->front("queue1");
    ASSERT_NE(msg2.get(), nullptr);
    ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
    ASSERT_EQ(mmp->getable_count("queue1"), 2);
    ASSERT_EQ(mmp->waitack_count("queue1"), 2);

    rabbitmq::MessagePtr msg3 = mmp->front("queue1");
    ASSERT_NE(msg3.get(), nullptr);
    ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
    ASSERT_EQ(mmp->getable_count("queue1"), 1);
    ASSERT_EQ(mmp->waitack_count("queue1"), 3);

    rabbitmq::MessagePtr msg4 = mmp->front("queue1");
    ASSERT_NE(msg4.get(), nullptr);
    ASSERT_EQ(msg4->payload().body(), std::string("Hello World-4"));
    ASSERT_EQ(mmp->getable_count("queue1"), 0);
    ASSERT_EQ(mmp->waitack_count("queue1"), 4);
}



int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new MessageTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_msgtest.cpp ../mqcommon/msg.pb.cc -o mq_msgtest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ 
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_msgtest
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[DBG][21:51:40][../mqserver/message.hpp:100]    向临时文件写入数据: Hello World-1
[DBG][21:51:40][../mqserver/message.hpp:100]    向临时文件写入数据: Hello World-2
[DBG][21:51:40][../mqserver/message.hpp:100]    向临时文件写入数据: Hello World-3
[DBG][21:51:40][../mqserver/message.hpp:100]    向临时文件写入数据: Hello World-4
[----------] 1 test from message_test2
[ RUN      ] message_test2.recovery_test
[       OK ] message_test2.recovery_test (0 ms)
[----------] 1 test from message_test2 (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (1 ms total)
[  PASSED  ] 1 test.

恢复历史数据的时候,只有4条消息是持久化存储的,第五条消息"Hello World-5"是没有持久化的,所以我们恢复的时候只需要恢复4条持久化的历史数据即可

删除消息测试:

cpp 复制代码
#include "../mqserver/message.hpp"
#include <gtest/gtest.h>

rabbitmq::MessageManager::ptr mmp;

class MessageTest : public testing::Environment 
{
public:
virtual void SetUp() override 
{
	mmp = std::make_shared<rabbitmq::MessageManager>("./data/message/");
	mmp->initQueueMessage("queue1");
}
virtual void TearDown() override 
{
	//mmp->clear();
}
};

//新增消息测试:新增消息,然后观察可获取消息数量,以及持久化消息数量
TEST(message_test, insert_test) 
{
	rabbitmq::BasicProperties properties;
	properties.set_id(rabbitmq::UUIDHelper::uuid());
	properties.set_delivery_mode(rabbitmq::DeliveryMode::DURABLE);
	properties.set_routing_key("news.music.pop");
	mmp->insert("queue1", &properties, "Hello World-1", true);
	mmp->insert("queue1", nullptr, "Hello World-2", true);
	mmp->insert("queue1", nullptr, "Hello World-3", true);
	mmp->insert("queue1", nullptr, "Hello World-4", true);
	mmp->insert("queue1", nullptr, "Hello World-5", false);
	ASSERT_EQ(mmp->getable_count("queue1"), 5);
	ASSERT_EQ(mmp->total_count("queue1"), 4);
	ASSERT_EQ(mmp->durable_count("queue1"), 4);
	ASSERT_EQ(mmp->waitack_count("queue1"), 0);
}


//获取消息测试:获取一条消息,然后在不进行确认的情况下,查看可获取消息数量,待确认消息数量,以及测试消息获取的顺序
// TEST(message_test, select_test) 
// {
//     ASSERT_EQ(mmp->getable_count("queue1"), 5);
//     rabbitmq::MessagePtr msg1 = mmp->front("queue1");
//     ASSERT_NE(msg1.get(), nullptr);
//     ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 4);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 1);

//     rabbitmq::MessagePtr msg2 = mmp->front("queue1");
//     ASSERT_NE(msg2.get(), nullptr);
//     ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 3);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 2);

//     rabbitmq::MessagePtr msg3 = mmp->front("queue1");
//     ASSERT_NE(msg3.get(), nullptr);
//     ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 2);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 3);

//     rabbitmq::MessagePtr msg4 = mmp->front("queue1");
//     ASSERT_NE(msg4.get(), nullptr);
//     ASSERT_EQ(msg4->payload().body(), std::string("Hello World-4"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 1);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 4);

//     rabbitmq::MessagePtr msg5 = mmp->front("queue1");
//     ASSERT_NE(msg5.get(), nullptr);
//     ASSERT_EQ(msg5->payload().body(), std::string("Hello World-5"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 0);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 5);
// }

//删除消息测试:确认一条消息,查看持久化消息数量,待确认消息数量
TEST(message_test, delete_test) 
{
	ASSERT_EQ(mmp->getable_count("queue1"), 5);
	rabbitmq::MessagePtr msg1 = mmp->front("queue1");
	ASSERT_NE(msg1.get(), nullptr);
	ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
	ASSERT_EQ(mmp->getable_count("queue1"), 4);
    ASSERT_EQ(mmp->waitack_count("queue1"), 1);
    mmp->ack("queue1", msg1->payload().properties().id());
    ASSERT_EQ(mmp->waitack_count("queue1"), 0);
    ASSERT_EQ(mmp->durable_count("queue1"), 3);
    ASSERT_EQ(mmp->total_count("queue1"), 4);
}

//恢复历史数据测试
// TEST(message_test2, recovery_test) 
// {
//     ASSERT_EQ(mmp->getable_count("queue1"), 4);
//     rabbitmq::MessagePtr msg1 = mmp->front("queue1");
//     ASSERT_NE(msg1.get(), nullptr);
//     ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 3);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 1);

//     rabbitmq::MessagePtr msg2 = mmp->front("queue1");
//     ASSERT_NE(msg2.get(), nullptr);
//     ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 2);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 2);

//     rabbitmq::MessagePtr msg3 = mmp->front("queue1");
//     ASSERT_NE(msg3.get(), nullptr);
//     ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 1);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 3);

//     rabbitmq::MessagePtr msg4 = mmp->front("queue1");
//     ASSERT_NE(msg4.get(), nullptr);
//     ASSERT_EQ(msg4->payload().body(), std::string("Hello World-4"));
//     ASSERT_EQ(mmp->getable_count("queue1"), 0);
//     ASSERT_EQ(mmp->waitack_count("queue1"), 4);
// }



int main(int argc,char *argv[])
{
    testing::InitGoogleTest(&argc, argv);
    testing::AddGlobalTestEnvironment(new MessageTest);
    return RUN_ALL_TESTS();
}

运行结果:

bash 复制代码
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_msgtest.cpp ../mqcommon/msg.pb.cc -o mq_msgtest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_msgtest
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from message_test
[ RUN      ] message_test.insert_test
[       OK ] message_test.insert_test (0 ms)
[ RUN      ] message_test.delete_test
[       OK ] message_test.delete_test (0 ms)
[----------] 2 tests from message_test (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (1 ms total)
[  PASSED  ] 2 tests.
相关推荐
froginwe117 小时前
AJAX 实例详解
开发语言
魔力军7 小时前
Rust学习Day2: 变量与可变性、数据类型和函数和控制流
开发语言·学习·rust
sycmancia7 小时前
C++——强制类型转化、const的理解
开发语言·c++
hzb666667 小时前
unictf2026
开发语言·javascript·安全·web安全·php
我在人间贩卖青春7 小时前
C++之面向对象编程多文件文件示例
c++
燃于AC之乐7 小时前
深入解剖STL deque:从源码剖析到容器适配器实现
开发语言·c++·stl·源码剖析·容器实现
kaikaile19957 小时前
基于MATLAB的滑动轴承弹流润滑仿真程序实现
开发语言·matlab
这周也會开心7 小时前
RabbitMQ知识点
分布式·rabbitmq
禹凕7 小时前
Python编程——进阶知识(MYSQL引导入门)
开发语言·python·mysql