【消息队列】交换机数据管理实现

目录

一.交换机数据管理

二.交换机数据类实现

三.交换机数据持久化类实现

四.交换机数据管理类实现

五.代码整合

六.交换机数据管理测试


我们交换机的所有类,都存放在mqserver/exchange.hpp里面

好了,我们现在开始设计

一.交换机数据管理

1.定义交换机数据类

  • a. 交换机名称
  • b. 交换机类型:
  • c. 是否持久化标志:指示交换机是否需要在服务器重启后继续存在
  • d. 是否自动删除标志:当所有绑定队列都取消绑定时,是否自动删除该交换机
  • f.其他参数

我们很快就能写出下面这个初版(注意是第一版,不代表最终效果)

cpp 复制代码
// 1. 定义交换机数据结构体
// 该类表示系统中的交换机实体,包含交换机的所有配置属性
struct Exchange
{
    using ptr = std::shared_ptr<Exchange>; // 智能指针别名,便于管理生命周期

    std::string name;                                  // 交换机唯一标识名称
    mymq::ExchangeType type;                           // 交换机类型:DIRECT、FANOUT、TOPIC,注意mymq::ExchangeType是我们的.proto文件编译生成的类
    bool durable;                                      // 持久化标志:true-持久化,false-非持久化
    bool auto_delete;                                  // 自动删除标志:true-无绑定时自动删除
    std::unordered_map<std::string, std::string> args; // 额外参数

    // 默认构造函数
    Exchange() {}

    // 带参构造函数,初始化所有成员
    Exchange(const std::string &ename, // 交换机名称
             mymq::ExchangeType etype,       // 交换机类型
             bool edurable,            // 交换机持久化标志
             bool eauto_delete,        // 交换机自动删除标志
             std::unordered_map<std::string, std::string> &eargs) : name(ename), type(etype), durable(edurable),
                                                                    auto_delete(eauto_delete), args(eargs)
    {
    }

    // 我们传进去的字符串str_args的格式是:key1=value1&key2=value2&...
    // 那么我们就在这个函数里面对这个str_args进行拆分,然后把拆分出来的信息填充到成员变量args里(他是一个unordered_map)
    void setArgs(const std::string &str_args) {}

    // 将成员变量args里面的信息序列化为格式化字符串
    // 返回格式:key1=value1&key2=value2&...
    std::string getArgs() {}
};

2.定义交换机数据持久化类(数据持久化的sqlite3数据库中)

  • a. 创建/删除交换机数据表
  • b. 新增交换机数据
  • c. 删除交换机数据
  • d. 查询所有交换机数据
  • e. 查询指定交换机数据(根据名称)

我们很快就能写出下面这个初版(注意是第一版,不代表最终效果)

cpp 复制代码
// 交换机映射表类型:以交换机名称为键,指向Exchange对象的智能指针为值
using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;

// 2. 交换机数据持久化管理类
// 负责将交换机数据存储到SQLite数据库,提供基本的CRUD操作
class ExchangeMapper
{
public:
    // 构造函数,初始化数据库连接
    // dbfile: SQLite数据库文件路径
    ExchangeMapper(const std::string &dbfile) : _sql_helper(dbfile)
    {
    }
    // 创建交换机数据表
    void createTable()
    {
    }
    // 删除交换机数据表
    void removeTable()
    {
    }
    // 插入新交换机数据
    bool insert(Exchange::ptr &exp) // Exchange::ptr是一个std::shared_ptr<Exchange>
    {
    }
    // 删除指定名称的交换机数据
    void remove(const std::string &name)
    {
    }

    //getOne:根据名称从数据库中查询一个交换机,并返回一个Exchange::ptr
    //Exchange::ptr是一个std::shared_ptr<Exchange>
    Exchange::ptr getOne(const std::string &name) {}

    //getAll:从数据库中获取所有交换机,并返回一个ExchangeMap
    // ExchangeMap是std::unordered_map<std::string, Exchange::ptr>
    std::unordered_map<std::string, Exchange::ptr> getAll() {}

private:
    SqliteHelper _sql_helper; // SQLite数据库操作辅助类实例
};
  1. 定义交换机数据管理类
  • a. 声明交换机,并添加管理(存在则OK,不存在则创建)
  • b. 删除交换机
  • c. 获取指定交换机
  • d. 销毁所有交换机数据'

我们很快就能写出下面这个初版(注意是第一版,不代表最终效果)

cpp 复制代码
// 3. 交换机数据内存管理类
// 管理内存中的交换机数据,协调内存与持久化存储的同步
class ExchangeManager
{
public:
    using ptr = std::shared_ptr<ExchangeManager>; // 智能指针别名

    // 构造函数,初始化持久化映射器并恢复数据
    // dbfile: 数据库文件路径
    ExchangeManager(const std::string &dbfile) : _mapper(dbfile)
    {
    }

    // 声明(创建或获取)交换机
    bool declareExchange(const std::string &name,
                         mymq::ExchangeType type,
                         bool durable,
                         bool auto_delete,
                         std::unordered_map<std::string, std::string> args)
    {
    }

    // 删除指定名称的交换机
    void deleteExchange(const std::string &name)
    {
    }
    // 获取指定名称的交换机对象
    //Exchange::ptr是一个std::shared_ptr<Exchange>
    Exchange::ptr selectExchange(const std::string &name)
    {
    }
    // 判断指定名称的交换机是否存在
    bool exists(const std::string &name)
    {
    }
    // 获取当前内存中的交换机数量
    size_t size()
    {
    }
    // 清理所有交换机数据(从内存和数据库)
    void clear()
    {
    }
private:
    std::mutex _mutex;      // 互斥锁,保证线程安全
    ExchangeMapper _mapper; // 持久化管理器实例
    ExchangeMap _exchanges; // 内存中的交换机映射表,注意ExchangeMap其实是std::unordered_map<std::string, Exchange::ptr>;
};

那么到这里我们的模板算是搭建出来了,不代表最终效果!!!

二.交换机数据类实现

说实话这个类是真的没有什么技术含量

cpp 复制代码
// 1. 定义交换机数据结构体
// 该类表示系统中的交换机实体,包含交换机的所有配置属性
struct Exchange
{
    using ptr = std::shared_ptr<Exchange>; // 智能指针别名,便于管理生命周期

    std::string name;                                  // 交换机唯一标识名称
    mymq::ExchangeType type;                           // 交换机类型:DIRECT、FANOUT、TOPIC,注意mymq::ExchangeType是我们的.proto文件编译生成的类
    bool durable;                                      // 持久化标志:true-持久化,false-非持久化
    bool auto_delete;                                  // 自动删除标志:true-无绑定时自动删除
    std::unordered_map<std::string, std::string> args; // 额外参数

    // 默认构造函数
    Exchange() {}

    // 带参构造函数,初始化所有成员
    Exchange(const std::string &ename, // 交换机名称
             mymq::ExchangeType etype, // 交换机类型
             bool edurable,            // 交换机持久化标志
             bool eauto_delete,        // 交换机自动删除标志
             std::unordered_map<std::string, std::string> &eargs) : name(ename), type(etype), durable(edurable),
                                                                    auto_delete(eauto_delete), args(eargs)
    {}

    // 我们传进去的字符串str_args的格式是:key1=value1&key2=value2&...
    // 那么我们就在这个函数里面对这个str_args进行拆分,然后把拆分出来的信息填充到成员变量args里(他是一个unordered_map)
    void setArgs(const std::string &str_args)
    {
        std::vector<std::string> sub_args;
        mymq::StringHelper::split(str_args, "&", sub_args); // 按'&'分割字符串,将分割的字串全部放到sub_args
        // 此时每个字串的样子都是key1=value1
        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;                       // 存储到map中
        }
    }

    // 将成员变量args里面的信息序列化为格式化字符串
    // 返回格式:key1=value1&key2=value2&...
    std::string getArgs()
    {
        std::string result;
        for (auto start = args.begin(); start != args.end(); ++start) // 遍历args变量里面的所有元素
        {
            result += start->first + "=" + start->second + "&"; // 组织字符串
        }
        return result;
    }
};

我们很容易就写出完整的代码

不过这不是最终版本,只是代表我们作为一个普通人应该有的思路去写出的代码

三.交换机数据持久化类实现

stringstream类讲解

在C++中,stringstream是一个类,它允许你将字符串当作流来处理。这意味着你可以像使用cin和cout那样,从字符串中读取数据,或者将数据写入字符串。

它位于<sstream>头文件中。

主要用途:

  1. 字符串分割(通过空格、换行等分隔符)

  2. 数据类型转换:将数字字符串转换为数值类型,或者将数值类型转换为字符串。

  3. 字符串拼接:将不同类型的数据拼接成一个字符串。

基本操作:

  • 创建一个stringstream对象:可以使用字符串初始化,也可以不初始化。

  • 向stringstream对象写入数据:使用 << 操作符,就像向cout写入一样。

  • 从stringstream对象读取数据:使用 >> 操作符,就像从cin读取一样。

  • 获取stringstream中的字符串:使用.str()方法。

我们举一些简单的例子来说明

示例1:将数据组合成字符串

cpp 复制代码
#include <iostream>
#include <sstream>  // 需要包含这个头文件
#include <string>

int main() {
    std::stringstream ss;  // 创建 stringstream 对象
    
    int age = 25;
    std::string name = "张三";
    double score = 89.5;
    
    // 将不同类型的数据写入字符串流
    ss << "姓名:" << name << ",年龄:" << age << ",分数:" << score;
    
    // 获取组合后的字符串
    std::string result = ss.str();
    std::cout << result << std::endl;
    // 输出:姓名:张三,年龄:25,分数:89.5
    
    return 0;
}

示例2:从字符串提取数据

cpp 复制代码
#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::string data = "张三 25 89.5";
    std::stringstream ss(data);  // 用字符串初始化
    
    std::string name;
    int age;
    double score;
    
    // 从流中提取数据,按空格分隔
    ss >> name >> age >> score;
    
    std::cout << "姓名:" << name << std::endl;
    std::cout << "年龄:" << age << std::endl;
    std::cout << "分数:" << score << std::endl;
    
    return 0;
}

示例3:数据类型转换

cpp 复制代码
#include <iostream>
#include <sstream>
#include <string>

int main() {
    // 数字转字符串
    std::stringstream ss1;
    int num = 123;
    ss1 << num;  // 将整数写入流
    std::string str_num = ss1.str();  // 获取字符串形式的数字
    std::cout << "字符串:" << str_num << std::endl;
    
    // 字符串转数字
    std::string str = "456";
    std::stringstream ss2(str);
    int value;
    ss2 >> value;  // 从流中提取整数
    std::cout << "数字:" << value << std::endl;
    
    return 0;
}

常用成员函数

  1. str()

获取流中的字符串

可以带参数设置新字符串:ss.str("新内容")

  1. clear()

清除错误状态标志(如文件结束标志)

注意:不会清除内容

  1. 示例:重置 stringstream
cpp 复制代码
#include <iostream>
#include <sstream>

int main() {
    std::stringstream ss;
    
    // 第一次使用
    ss << "Hello";
    std::cout << ss.str() << std::endl;  // 输出:Hello
    
    // 重置:清空内容并清除状态
    ss.str("");  // 清空内容
    ss.clear();  // 清除状态标志
    
    // 再次使用
    ss << "World";
    std::cout << ss.str() << std::endl;  // 输出:World
    
    return 0;
}

实现

cpp 复制代码
// 交换机映射表类型:以交换机名称为键,指向Exchange对象的智能指针为值
using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;

// 2. 交换机数据持久化管理类
// 负责将交换机数据存储到SQLite数据库,提供基本的CRUD操作
class ExchangeMapper
{
public:
    // 构造函数,初始化数据库连接
    // dbfile: SQLite数据库文件路径
    ExchangeMapper(const std::string &dbfile) : _sql_helper(dbfile)
    {
        std::string path = mymq::FileHelper::parentDirectory(dbfile); // 我们先获取数据库文件的父目录
        mymq::FileHelper::createDirectory(path);                      // 确保数据库目录存在
        assert(_sql_helper.open());                                   // 打开数据库连接
        createTable();                                                // 创建数据表
    }
    // 创建交换机数据表
    void createTable()
    {
        std::string CREATE_TABLE =
            "create table if not exists exchange_table("
            "name varchar(32) primary key, " // 交换机名称,主键,最长32字符
            "type int, "                     // 交换机类型,整数表示
            "durable int, "                  // 持久化标志,0或1
            "auto_delete int, "              // 自动删除标志,0或1
            "args varchar(128));";           // 额外参数,序列化字符串格式

        bool ret = _sql_helper.exec(CREATE_TABLE, nullptr, nullptr);//执行SQL语句,SQL语句没有查询结果,不需要回调函数
        if (ret == false)
        {
            DLOG("创建交换机数据库表失败!!"); // 记录错误日志
            abort();                            // 表创建失败,终止程序
        }
    }
    // 删除交换机数据表
    void removeTable()
    {
        std::string  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) // Exchange::ptr是一个std::shared_ptr<Exchange>
    {
        //本质就是往exchange_table表里面插入一条数据
        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() << "');"; // 序列化参数
        //这个时候ss就已经是一条SQL语句了,往exchange_table里面插入一条交换机的数据
        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 << "';";
           bool ret= _sql_helper.exec(ss.str(), nullptr, nullptr);
            if (ret == false)
            {
                DLOG("删除交换机%s表失败!!",name);
                abort(); // 交换机删除失败,终止程序
            }
    }

    // 将数据库里面的exchange_table表里面的所有交换机数据加载到内存
    // 返回值: 包含所有交换机的映射表
    //注意ExchangeMap是std::unordered_map<std::string, Exchange::ptr>
    //而这个Exchange::ptr是std::shared_ptr<Exchange>
    ExchangeMap recovery()
    {
        ExchangeMap result;
        // 查询所有字段
        std::string sql = "select name, type, durable, auto_delete, args from exchange_table";
        _sql_helper.exec(sql, selectCallback, &result);  // 回调函数selectCallback处理查询结果,并且将result传递给了selectCallback
        return result; 
    }

private:
    // SQLite查询结果回调函数------注意:只有recovery()函数才会使用到这个回调函数,所以这个函数是为recovery()特制的
    // arg: 用户自定义参数,此处为ExchangeMap指针,我们在上面传入了&result,把所有的数据都
    // numcol: 返回的列数
    // row: 当前行的每一列的数据
    // fields: 记录了每一列的名称
    static int selectCallback(void* arg,int numcol,char** row,char** fields) { 
        ExchangeMap *result = (ExchangeMap*)arg;

        auto exp = std::make_shared<Exchange>();  // 创建新Exchange对象

        exp->name = row[0];                      // 名称
        exp->type = (mymq::ExchangeType)std::stoi(row[1]);  // 这里可是交换机类型:mymq::ExchangeType,这里进行了类型转换
        exp->durable = (bool)std::stoi(row[2]);  // 持久化标志
        exp->auto_delete = (bool)std::stoi(row[3]); // 自动删除标志
        if (row[4]) //说明存在其他参数
        {
            exp->setArgs(row[4]);        // 解析参数字符串
        }

        //这里我们就把临时对象的数据添加进我们传进来的这个ExchangeMap里面
        result->insert(std::make_pair(exp->name, exp)); // 插入到映射表:[交换机名称,交换机对象]
        return 0; 
    } 

    mymq::SqliteHelper _sql_helper; // SQLite数据库操作辅助类实例
};

这个可能和我们最初的想法其实是有点不一样的,但是不要紧

四.交换机数据管理类实现

cpp 复制代码
// 3. 交换机数据内存管理类
// 管理内存中的交换机数据,协调内存与持久化存储的同步
class ExchangeManager
{
public:
    using ptr = std::shared_ptr<ExchangeManager>; // 智能指针别名

    // 构造函数,初始化持久化映射器并恢复数据
    // dbfile: 数据库文件路径
    ExchangeManager(const std::string &dbfile) : _mapper(dbfile)
    {
        _exchanges = _mapper.recovery();  // 将数据库里面的exchange_table表里面的所有交换机数据加载到内存
    }

    // 声明(创建或获取)交换机
    bool declareExchange(const std::string &name,//交换机名称
                         mymq::ExchangeType type,//交换机类型
                         bool durable,//持久化标志
                         bool auto_delete,//自动删除标志
                         std::unordered_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);//创建Exchange临时对象
        if (durable == true) { //持久化标志为true
            // 如果交换机需要持久化,先写入数据库
            bool 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;  // 交换机不存在,直接返回
        } 
        
        if(it->second->durable == true) //持久化存储标志为true,才需要去数据库删除,否则只需要在内存中删除即可
        {    
            _mapper.remove(name);          // 从数据库删除持久化数据
        }
        _exchanges.erase(name);            // 从内存映射表删除

    }
    // 获取指定名称的交换机对象
    // Exchange::ptr是一个std::shared_ptr<Exchange>
    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; // 内存中的交换机映射表,注意ExchangeMap其实是std::unordered_map<std::string, Exchange::ptr>;
};

五.代码整合

我们把代码都放到mqserver/exchange.hpp里面

我们往里面写入下面这些

cpp 复制代码
#ifndef __M_EXCHANGE_H__
#define __M_EXCHANGE_H__

#include "../mqcommon/logger.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"
#include <google/protobuf/map.h>
#include <iostream>
#include <unordered_map>
#include <mutex>
#include <memory>

namespace mymq
{
    // 1. 定义交换机数据结构体
    // 该类表示系统中的交换机实体,包含交换机的所有配置属性
    struct Exchange
    {
        using ptr = std::shared_ptr<Exchange>; // 智能指针别名,便于管理生命周期

        std::string name;                                  // 交换机唯一标识名称
        mymq::ExchangeType type;                           // 交换机类型:DIRECT、FANOUT、TOPIC,注意mymq::ExchangeType是我们的.proto文件编译生成的类
        bool durable;                                      // 持久化标志:true-持久化,false-非持久化
        bool auto_delete;                                  // 自动删除标志:true-无绑定时自动删除
        std::unordered_map<std::string, std::string> args; // 额外参数

        // 默认构造函数
        Exchange() {}

        // 带参构造函数,初始化所有成员
        Exchange(const std::string &ename, // 交换机名称
                 mymq::ExchangeType etype, // 交换机类型
                 bool edurable,            // 交换机持久化标志
                 bool eauto_delete,        // 交换机自动删除标志
                 std::unordered_map<std::string, std::string> &eargs) : name(ename), type(etype), durable(edurable),
                                                                        auto_delete(eauto_delete), args(eargs)
        {
        }

        // 我们传进去的字符串str_args的格式是:key1=value1&key2=value2&...
        // 那么我们就在这个函数里面对这个str_args进行拆分,然后把拆分出来的信息填充到成员变量args里(他是一个unordered_map)
        void setArgs(const std::string &str_args)
        {
            std::vector<std::string> sub_args;
            mymq::StringHelper::split(str_args, "&", sub_args); // 按'&'分割字符串,将分割的字串全部放到sub_args
            // 此时每个字串的样子都是key1=value1
            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;                       // 存储到map中
            }
        }

        // 将成员变量args里面的信息序列化为格式化字符串
        // 返回格式:key1=value1&key2=value2&...
        std::string getArgs()
        {
            std::string result;
            for (auto start = args.begin(); start != args.end(); ++start) // 遍历args变量里面的所有元素
            {
                result += start->first + "=" + start->second + "&"; // 组织字符串
            }
            return result;
        }
    };

    // 交换机映射表类型:以交换机名称为键,指向Exchange对象的智能指针为值
    using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;

    // 2. 交换机数据持久化管理类
    // 负责将交换机数据存储到SQLite数据库,提供基本的CRUD操作
    class ExchangeMapper
    {
    public:
        // 构造函数,初始化数据库连接
        // dbfile: SQLite数据库文件路径
        ExchangeMapper(const std::string &dbfile) : _sql_helper(dbfile)
        {
            std::string path = mymq::FileHelper::parentDirectory(dbfile); // 我们先获取数据库文件的父目录
            mymq::FileHelper::createDirectory(path);                      // 确保数据库目录存在
            assert(_sql_helper.open());                                   // 打开数据库连接
            createTable();                                                // 创建数据表
        }
        // 创建交换机数据表
        void createTable()
        {
            std::string CREATE_TABLE =
                "create table if not exists exchange_table("
                "name varchar(32) primary key, " // 交换机名称,主键,最长32字符
                "type int, "                     // 交换机类型,整数表示
                "durable int, "                  // 持久化标志,0或1
                "auto_delete int, "              // 自动删除标志,0或1
                "args varchar(128));";           // 额外参数,序列化字符串格式

            bool ret = _sql_helper.exec(CREATE_TABLE, nullptr, nullptr); // 执行SQL语句,SQL语句没有查询结果,不需要回调函数
            if (ret == false)
            {
                DLOG("创建交换机数据库表失败!!"); // 记录错误日志
                abort();                            // 表创建失败,终止程序
            }
        }
        // 删除交换机数据表
        void removeTable()
        {
            std::string 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) // Exchange::ptr是一个std::shared_ptr<Exchange>
        {
            // 本质就是往exchange_table表里面插入一条数据
            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() << "');"; // 序列化参数
            // 这个时候ss就已经是一条SQL语句了,往exchange_table里面插入一条交换机的数据
            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 << "';";
           bool ret= _sql_helper.exec(ss.str(), nullptr, nullptr);
            if (ret == false)
            {
                DLOG("删除交换机%s失败!!",name);
                abort(); // 交换机删除失败,终止程序
            }
        }

        // 将数据库里面的exchange_table表里面的所有交换机数据加载到内存
        // 返回值: 包含所有交换机的映射表
        // 注意ExchangeMap是std::unordered_map<std::string, Exchange::ptr>
        // 而这个Exchange::ptr是std::shared_ptr<Exchange>
        ExchangeMap recovery()
        {
            ExchangeMap result;
            // 查询所有字段
            std::string sql = "select name, type, durable, auto_delete, args from exchange_table";
            _sql_helper.exec(sql, selectCallback, &result); // 回调函数selectCallback处理查询结果,并且将result传递给了selectCallback
            return result;
        }

    private:
        // SQLite查询结果回调函数------注意:只有recovery()函数才会使用到这个回调函数,所以这个函数是为recovery()特制的
        // arg: 用户自定义参数,此处为ExchangeMap指针,我们在上面传入了&result,把所有的数据都
        // numcol: 返回的列数
        // row: 当前行的每一列的数据
        // fields: 记录了每一列的名称
        static int selectCallback(void *arg, int numcol, char **row, char **fields)
        {
            ExchangeMap *result = (ExchangeMap *)arg;

            auto exp = std::make_shared<Exchange>(); // 创建新Exchange对象

            exp->name = row[0];                                // 名称
            exp->type = (mymq::ExchangeType)std::stoi(row[1]); // 这里可是交换机类型:mymq::ExchangeType,这里进行了类型转换
            exp->durable = (bool)std::stoi(row[2]);            // 持久化标志
            exp->auto_delete = (bool)std::stoi(row[3]);        // 自动删除标志
            if (row[4])                                        // 说明存在其他参数
            {
                exp->setArgs(row[4]); // 解析参数字符串
            }

            // 这里我们就把临时对象的数据添加进我们传进来的这个ExchangeMap里面
            result->insert(std::make_pair(exp->name, exp)); // 插入到映射表:[交换机名称,交换机对象]
            return 0;
        }

        mymq::SqliteHelper _sql_helper; // SQLite数据库操作辅助类实例
    };

    // 3. 交换机数据内存管理类
    // 管理内存中的交换机数据,协调内存与持久化存储的同步
    class ExchangeManager
    {
    public:
        using ptr = std::shared_ptr<ExchangeManager>; // 智能指针别名

        // 构造函数,初始化持久化映射器并恢复数据
        // dbfile: 数据库文件路径
        ExchangeManager(const std::string &dbfile) : _mapper(dbfile)
        {
            _exchanges = _mapper.recovery(); // 将数据库里面的exchange_table表里面的所有交换机数据加载到内存
        }

        // 声明(创建或获取)交换机
        bool declareExchange(const std::string &name, // 交换机名称
                             mymq::ExchangeType type, // 交换机类型
                             bool durable,            // 持久化标志
                             bool auto_delete,        // 自动删除标志
                             std::unordered_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); // 创建Exchange临时对象
            if (durable == true)
            { // 持久化标志为true
                // 如果交换机需要持久化,先写入数据库
                bool 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; // 交换机不存在,直接返回
            }

            if (it->second->durable == true) // 持久化存储标志为true,才需要去数据库删除,否则只需要在内存中删除即可
            {
                _mapper.remove(name); // 从数据库删除持久化数据
            }
            _exchanges.erase(name); // 从内存映射表删除
        }
        // 获取指定名称的交换机对象
        // Exchange::ptr是一个std::shared_ptr<Exchange>
        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; // 内存中的交换机映射表,注意ExchangeMap其实是std::unordered_map<std::string, Exchange::ptr>;
    };
}
#endif

六.交换机数据管理测试

接下来我们就借助这个GTest来对这个进行测试

测试插入数据,查询数据

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

//ExchangeManager::ptr其实是std::shared_ptr<ExchangeManager>
mymq::ExchangeManager::ptr emp;

//全局事件
class ExchangeTest : public testing::Environment {
public:
    // 在所有测试开始之前执行
    virtual void SetUp() override {
        emp=std::make_shared<mymq::ExchangeManager>("./data/test.db");//创建数据库文件,并做好初始化
    }
    // 在所有测试结束之后执行
    virtual void TearDown() override {
        emp->clear();//清理所有交换机数据
    }
};

//插入数据测试用例
TEST(exchange_test,insert_test)
{
    //往数据库表里面插入几条条交换机的数据
    std::unordered_map<std::string,std::string> map={{"k1","v1"},{"k2","v2"}};
    emp->declareExchange("exchange1",mymq::ExchangeType::DIRECT,true,false,map);
    emp->declareExchange("exchange2",mymq::ExchangeType::DIRECT,true,false,map);
    emp->declareExchange("exchange3",mymq::ExchangeType::DIRECT,true,false,map);
    emp->declareExchange("exchange4",mymq::ExchangeType::DIRECT,true,false,map);
    ASSERT_EQ(emp->size(),4);//判断当前数据库内的数据是不是有4条
}

//查询数据测试用例
TEST(exchange_test,select_test)
{
    //查询exchange3的信息
    mymq::Exchange::ptr exp=emp->selectExchange("exchange3");
    ASSERT_EQ(exp->name,"exchange3");
    ASSERT_EQ(exp->durable,true);
    ASSERT_EQ(exp->auto_delete,false);
    ASSERT_EQ(exp->type,mymq::ExchangeType::DIRECT);
    ASSERT_EQ(exp->getArgs(),std::string("k2=v2&k1=v1&"));
}


int main(int argc, char *argv[]) { 
    testing::InitGoogleTest(&argc, argv); 
    testing::AddGlobalTestEnvironment(new ExchangeTest());  // 注册全局环境
    return RUN_ALL_TESTS(); 
}

测试是一点问题都没有。

也可以看到,整个测试执行完之后,数据库里面什么表都没有

删除测试

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

//ExchangeManager::ptr其实是std::shared_ptr<ExchangeManager>
mymq::ExchangeManager::ptr emp;

//全局事件
class ExchangeTest : public testing::Environment {
public:
    // 在所有测试开始之前执行
    virtual void SetUp() override {
        emp=std::make_shared<mymq::ExchangeManager>("./data/test.db");//创建数据库文件,并做好初始化
        //往里面插入一条数据
        std::unordered_map<std::string,std::string> map={{"k1","v1"},{"k2","v2"}};
        emp->declareExchange("exchange1",mymq::ExchangeType::DIRECT,true,false,map);
    }
    // 在所有测试结束之后执行
    virtual void TearDown() override {
        emp->clear();//清理所有交换机数据
    }
};

//删除数据测试用例
TEST(exchange_test,delete_test)
{
    emp->deleteExchange("exchange1");
    //查询exchange3的信息
    mymq::Exchange::ptr exp=emp->selectExchange("exchange1");
    ASSERT_EQ(exp,nullptr);
}

int main(int argc, char *argv[]) { 
    testing::InitGoogleTest(&argc, argv); 
    testing::AddGlobalTestEnvironment(new ExchangeTest());  // 注册全局环境
    return RUN_ALL_TESTS(); 
}

恢复数据测试

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

//ExchangeManager::ptr其实是std::shared_ptr<ExchangeManager>
mymq::ExchangeManager::ptr emp;

//全局事件
class ExchangeTest : public testing::Environment {
public:
    // 在所有测试开始之前执行
    virtual void SetUp() override {
        emp=std::make_shared<mymq::ExchangeManager>("./data/test.db");//创建数据库文件,并做好初始化
    }
};

//插入数据测试用例
TEST(exchange_test,insert_test)
{
    //往数据库表里面插入几条条交换机的数据
    std::unordered_map<std::string,std::string> map={{"k1","v1"},{"k2","v2"}};
    emp->declareExchange("exchange1",mymq::ExchangeType::DIRECT,true,false,map);
    emp->declareExchange("exchange2",mymq::ExchangeType::DIRECT,true,false,map);
    emp->declareExchange("exchange3",mymq::ExchangeType::DIRECT,true,false,map);
    emp->declareExchange("exchange4",mymq::ExchangeType::DIRECT,true,false,map);
    ASSERT_EQ(emp->size(),4);//判断当前数据库内的数据是不是有4条
}

//查询数据测试用例
TEST(exchange_test,select_test)
{
    //查询exchange3的信息
    mymq::Exchange::ptr exp=emp->selectExchange("exchange3");
    ASSERT_EQ(exp->name,"exchange3");
    ASSERT_EQ(exp->durable,true);
    ASSERT_EQ(exp->auto_delete,false);
    ASSERT_EQ(exp->type,mymq::ExchangeType::DIRECT);
    ASSERT_EQ(exp->getArgs(),std::string("k2=v2&k1=v1&"));
}


int main(int argc, char *argv[]) { 
    testing::InitGoogleTest(&argc, argv); 
    testing::AddGlobalTestEnvironment(new ExchangeTest());  // 注册全局环境
    return RUN_ALL_TESTS(); 
}

我们去数据库看看

数据都还在啊!!

那么我们把这个插入数据的测试删除掉,看看后面是不是能加载进来

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

//ExchangeManager::ptr其实是std::shared_ptr<ExchangeManager>
mymq::ExchangeManager::ptr emp;

//全局事件
class ExchangeTest : public testing::Environment {
public:
    // 在所有测试开始之前执行
    virtual void SetUp() override {
        emp=std::make_shared<mymq::ExchangeManager>("./data/test.db");//创建数据库文件,并做好初始化
    }
};

//查询数据测试用例
TEST(exchange_test,select_test)
{
    //查询exchange3的信息
    mymq::Exchange::ptr exp=emp->selectExchange("exchange3");
    ASSERT_EQ(exp->name,"exchange3");
    ASSERT_EQ(exp->durable,true);
    ASSERT_EQ(exp->auto_delete,false);
    ASSERT_EQ(exp->type,mymq::ExchangeType::DIRECT);
    ASSERT_EQ(exp->getArgs(),std::string("k2=v2&k1=v1&"));
}


int main(int argc, char *argv[]) { 
    testing::InitGoogleTest(&argc, argv); 
    testing::AddGlobalTestEnvironment(new ExchangeTest());  // 注册全局环境
    return RUN_ALL_TESTS(); 
}

没有一点问题,这就说明加载到了历史数据。

相关推荐
Logic1013 小时前
《Mysql数据库应用》 第2版 郭文明 实验6 数据库系统维护核心操作与思路解析
数据库·sql·mysql·学习笔记·计算机网络技术·形考作业·国家开放大学
AI Echoes4 小时前
构建一个LangChain RAG应用
数据库·python·langchain·prompt·agent
华硕之声4 小时前
WIN+R 指令大全
网络·数据·华硕
@nengdoudou4 小时前
KingbaseES支持 mysql 的find_in_set函数
数据库·mysql
被AI抢饭碗的人4 小时前
linux:线程概念与控制
网络
another heaven4 小时前
【软考 磁盘磁道访问时间】总容量等相关案例题型
linux·网络·算法·磁盘·磁道
摇滚侠4 小时前
面试实战 问题三十三 Spring 事务常用注解
数据库·spring·面试
梁萌4 小时前
保姆级的MySQL执行计划(Explain)解读
数据库·mysql·explain·执行计划
JIngJaneIL4 小时前
基于Java+ vue智慧医药系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot