message.hpp消息管理模块

目录

一.Message模块介绍

二.Message的定义

[1. DeliverMode 枚举](#1. DeliverMode 枚举)

[2. BasicAttributes 消息属性](#2. BasicAttributes 消息属性)

[3. Message 消息](#3. Message 消息)

[3.1 MessageSelf 内部消息结构](#3.1 MessageSelf 内部消息结构)

[3.2 Message 结构](#3.2 Message 结构)

三.MessageMapper的实现

类的主要功能与设计思路

成员变量

代码细节解析

[1. 构造函数](#1. 构造函数)

[2. 插入消息](#2. 插入消息)

[3. 删除消息](#3. 删除消息)

[4. 垃圾回收gc](#4. 垃圾回收gc)

[5. 恢复数据recover](#5. 恢复数据recover)

四.QueueMessage的实现

设计思路

成员变量

代码细节解析

构造函数

消息推送 (push)

获取消息 (front)

确认消息 (remove)

清理队列 (clear)

恢复消息 (recover)

垃圾回收 (Gc)

五.MessageManager的实现

六.message.hpp全部代码

一.Message模块介绍

由于消息数据需要在网络传输中处理,使用protobuf作为消息类型的定义工具。

protobuf 提供了内置的序列化和反序列化功能,使得消息的编解码操作更加简便。

值得注意的是,消息数据的存储并没有使用传统的数据库方式。由于消息长度的变化幅度较大,部分消息可能非常庞大,数据库的结构化存储并不适合处理这样的动态数据。因此,我们选择将消息直接存储在文件中进行管理。

在内存中,仅需要记录每条消息在文件中的位置以及对应的长度,方便管理。

为了更加高效地管理消息数据,我们以队列为单位进行管理

每个队列都拥有自己独立的数据存储文件和内存管理单元。

二.Message的定义

将网络通信的message使用protobuf实现,编写msg.proto,方便后续通信。

1. DeliverMode 枚举

cpp 复制代码
enum DeliverMode
{
    NORMAL = 0;
    UNDURABLE = 1;
    DURABLE = 2;
}

DeliverMode 枚举定义了消息的三种投递模式:

  • NORMAL (值为 0):普通投递模式,表示消息不具有特殊的持久化或非持久化属性。
  • UNDURABLE (值为 1):非持久投递模式,表示消息在传递过程中不需要持久化,即消息的生命周期较短,不要求在服务器崩溃时保持。
  • DURABLE (值为 2):持久投递模式,表示消息需要持久化存储,即消息在服务器崩溃或重启时仍需保存并恢复,以确保重要消息不会丢失。

2. BasicAttributes 消息属性

cpp 复制代码
message BasicAttributes
{
    string id = 1;
    DeliverMode deliver_mode = 2;
    string routine_key = 3;
}

BasicAttributes 消息定义了消息的三个核心属性:

  • id:唯一标识符,用于唯一标识每个消息。
  • deliver_mode:投递模式,使用上面定义的 DeliverMode 枚举类型,指示消息的投递方式。
  • routine_key:路由键,用于消息的路由选择,与消息队列的路由机制相关联

3. Message 消息

Message 消息是消息传递系统中最重要的结构,它包含实际的消息内容,属性及其相关的管理数据

cpp 复制代码
message Message
{
    message MessageSelf
    {
        BasicAttributes basic_attributes = 1;
        string body = 2;
        string valid = 3;
    };
    MessageSelf msg_self = 1;
    uint32 offset = 2;
    uint32 length = 3;
}
3.1 MessageSelf 内部消息结构

MessageSelfMessage 消息的内部消息结构,仅用于内部(如网络传输过程中)。

它包含三个字段:

  • basic_attributes:基本属性,包含消息的唯一标识符、投递模式和路由键。
  • body:消息体,表示实际的消息内容。
  • valid:用于验证消息是否合法或有效的字段。
3.2 Message 结构

Message 结构包含了整个消息的封装,它不仅包括 MessageSelf 内部消息结构,还包括额外的控制信息:

  • msg_self:内部消息结构 MessageSelf,包含消息的属性、内容和是否合法判断。
  • offset:偏移量,用于标识消息在传输或存储中的位置。
  • length:消息长度,表示消息内容的长度或大小,用于传输或存储时的边界确定。

三.MessageMapper的实现

类的主要功能与设计思路

  1. 消息持久化文件的创建和删除:

    • 每个消息队列都有对应的持久化文件(以 .data 为后缀)用于存储消息内容。
    • 在构造 MessageMapper 对象时,构造函数会检查并创建保存消息的目录,创建消息持久化文件(通过 createFile() 方法)。
    • removeFile() 方法用于删除消息持久化文件及其临时文件。
  2. 消息插入与删除:

    • insert() 方法负责将消息插入到持久化文件中。通过 _insert() 子函数来完成实际的写入操作。写入过程中,先写入消息的长度,再写入消息本体。
    • remove() 方法用于标记消息无效,而非真正删除。通过修改消息的 valid 字段为 "0" 来标记消息为无效,并且通过 FileHelper 类直接覆盖文件中的数据。
  3. 垃圾回收 (GC):

    • 在消息持久化过程中,由于消息被标记为无效,文件中可能会存在许多无效数据。为了释放空间,需要进行垃圾回收。
    • gc() 方法负责将所有有效消息加载到内存中,然后重新写入到一个临时文件(.tmp 后缀),完成后删除原始数据文件并将临时文件重命名为新的数据文件。这一过程可以有效清理文件中的无效消息。
  4. 恢复消息数据:

    • load() 方法从持久化文件中加载所有有效消息,消息的读取过程与写入过程相反,先读取消息的长度,再读取消息内容,并判断消息是否有效(通过 valid 字段),无效消息将被跳过。

成员变量

  • std::string _qname; // 队列名
  • std::string _dataFile; // 存储数据的文件名
  • std::string _tmpFile; // 临时文件名

代码细节解析

1. 构造函数
cpp 复制代码
MessageMapper(std::string &dir, const std::string qname)
    : _qname(qname)
{
    if (dir.back() != '/')
        dir.append("/");
    if (!FileHelper::createDir(dir))
    {
        ELOG("create dir %s failed", dir.c_str());
        assert(0);
    }
    _dataFile = dir + _qname + dataSuf;
    _tmpFile = dir + _qname + tmpSuf;
    createFile();
}
  • 构造函数接收保存消息的目录路径 dir 和队列名 qname,构造保存消息的文件路径 _dataFile 和临时文件路径 _tmpFile
  • 它会确保目录存在并创建相应的持久化文件。
2. 插入消息
cpp 复制代码
bool insert(msg_ptr &msg)
{
    return _insert(_dataFile, msg);
}

insert() 方法用于将消息插入到持久化文件中。实际的写入操作由 _insert() 方法完成。

cpp 复制代码
bool _insert(const std::string &name, msg_ptr &msg)
{
    std::string msg_str = msg->msg_self().SerializeAsString();
    FileHelper helper(name);
    size_t offset = helper.size();
    size_t start = offset;
    size_t len = msg_str.size();
    if (!helper.write((char *)&len, offset, sizeof(size_t)))
    {
        ELOG("%s:write msg length failed", name.c_str());
        return false;
    }
    offset += sizeof(len);
    if (!helper.write(msg_str.c_str(), offset, len))
    {
        ELOG("%s:write msg failed", name.c_str());
        return false;
    }
    offset += len;
    msg->set_length(len);
    msg->set_offset(start + sizeof(size_t));
    return true;
}
  • 先将消息序列化为字符串,然后将消息长度和内容依次写入文件。
  • 更新消息对象的 lengthoffset,方便管理和后续操作。
3. 删除消息

删除消息的过程是标记消息为无效 ,并覆盖文件中对应的消息内容。

cpp 复制代码
bool remove(msg_ptr &msg)
{
    msg->mutable_msg_self()->set_valid("0");
    size_t offset = msg->offset();
    std::string str = msg->msg_self().SerializeAsString();
    if (str.size() != msg->length())
    {
        ELOG("msg length is not equal to msg_self length");
        return false;
    }
    FileHelper helper(_dataFile);
    if (!helper.write(str.c_str(), offset, str.size()))
    {
        ELOG("%s:write msg failed", _dataFile.c_str());
        return false;
    }
    return true;
}
4. 垃圾回收gc
cpp 复制代码
std::list<msg_ptr> gc()
{
    std::list<msg_ptr> ret;
    if (!load(ret))
    {
        ELOG("recover failed");
        return ret;
    }
    FileHelper::createFile(_tmpFile);
    bool check = true;
    for (auto &pmsg : ret)
    {
        check = _insert(_tmpFile, pmsg);
        if (check == false)
        {
            DLOG("向临时文件写入消息数据失败!!");
            return ret;
        }
    }
    check = FileHelper::removeFile(_dataFile);
    if (!check)
    {
        ELOG("删除原数据文件失败!!");
        return ret;
    }
    FileHelper helper(_tmpFile);
    check = helper.rename(_dataFile);
    if (!check)
    {
        ELOG("重命名临时文件失败!!");
        return ret;
    }
    return ret;
}

gc() 方法执行垃圾回收操作,恢复所有有效消息,并将它们写入一个新的临时文件,最后替换掉原有的数据文件,并返回一个list,管理垃圾回收后文件中的有效消息。

5. 恢复数据recover
cpp 复制代码
bool load(std::list<msg_ptr> &ret)
{
    FileHelper helper(_dataFile);
    size_t offset = 0;
    size_t total_size = helper.size();
    while (offset < total_size)
    {
        std::string len_str;
        len_str.resize(sizeof(size_t));
        if (!helper.read(&len_str[0], offset, sizeof(size_t)))
        {
            ELOG("%s:read msg length failed", _dataFile.c_str());
            return false;
        }
        size_t len = 0;
        std::memcpy(&len, len_str.data(), sizeof(size_t));
        std::string msg_str;
        msg_str.resize(len);
        if (!helper.read(&msg_str[0], offset + sizeof(size_t), len))
        {
            ELOG("%s:read msg failed", _dataFile.c_str());
            return false;
        }
        offset += sizeof(size_t) + len;
        auto msg_ptr = std::make_shared<msg::Message>();
        msg_ptr->mutable_msg_self()->ParseFromString(msg_str);
        if (msg_ptr->msg_self().valid() == "0")
        {
            DLOG("msg is invalid");
            continue;
        }
        ret.push_back(msg_ptr);
    }
    return true;
}

load() 方法从持久化文件中读取所有有效的消息,跳过标记为无效的消息。

四.QueueMessage的实现

QueueMessage 是一个用于管理消息的队列类,主要负责消息的添加、持久化、获取、确认以及垃圾回收 等功能。它通过内存数据结构管理消息的生命周期,并与 MessageMapper 协作,实现消息的持久化存储与恢复。

设计思路

  • 消息管理 :通过 _pendings 列表管理待推送消息,_durables 哈希表管理持久化消息,_wait_acks 哈希表管理待确认消息。
  • 持久化管理 :使用 MessageMapper 类处理消息的持久化操作,确保了消息即使在系统重启后也能恢复。
  • 线程安全 :通过 std::mutex 保护所有对消息队列的操作,确保在多线程下的线程安全。
  • 垃圾回收机制 :通过 checkGC()Gc() 实现垃圾回收,防止持久化存储空间的冗余。

成员变量

  • _qname:队列名称,用于标识消息队列。
  • _mutex:互斥锁,确保对队列进行操作时的线程安全。
  • _mapperMessageMapper 对象,用于管理持久化消息的存储和恢复。
  • _pendings :待推送的消息列表,使用 std::list<msg_ptr> 管理。
  • _durables :持久化消息的哈希表,使用 std::unordered_map<std::string, msg_ptr> 管理。
  • _wait_acks :待确认消息的哈希表,使用 std::unordered_map<std::string, msg_ptr> 管理。
  • _valids_total:这两个变量分别记录持久化消息的有效数量和总数量,用于判断是否需要进行垃圾回收。

代码细节解析

构造函数
cpp 复制代码
 QueueMessage(std::string &dir, const std::string qname)
            : _qname(qname), _mapper(dir, qname)
        {
        }

填充队列名以及构造持久化管理句柄_mapper即可。

消息推送 (push)

功能:将消息添加队列到中,并根据消息的投递模式(DeliverMode)决定是否持久化。

cpp 复制代码
  bool push(const msg::BasicAttributes *pbasic, const std::string &body, bool qmode) // 新增消息
        {
            // 1.创建消息,并填充
            msg_ptr msg = std::make_shared<msg::Message>();
            if (pbasic == nullptr)
            {
                msg::DeliverMode dmode = qmode == true ? msg::DeliverMode::DURABLE : msg::DeliverMode::UNDURABLE;
                msg->mutable_msg_self()->mutable_basic_attributes()->set_id(UUIDHelper::uuid());
                msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
                msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key("default");
            }
            else
            {
                msg::DeliverMode dmode = qmode == true ? pbasic->deliver_mode() : msg::DeliverMode::UNDURABLE;
                msg->mutable_msg_self()->mutable_basic_attributes()->set_id(pbasic->id());
                msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
                msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key(pbasic->routine_key());
            }
            // 2.填充body
            msg->mutable_msg_self()->set_body(body);

            std::unique_lock<std::mutex> lock(_mutex);
            // 3.若需要持久化,则额外添加到持久化对应的_durables和文件中,并继续填充管理字段
            if (msg->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
            {
                msg->mutable_msg_self()->set_valid("1");
                bool ret = _mapper.insert(msg); // 先填充好管理字段,再添加到_durables中
                if (!ret)
                {
                    DLOG("持久化存储消息:%s 失败了!", body.c_str());
                    return false;
                }
                _durables[msg->msg_self().basic_attributes().id()] = msg;

                _total++;
                _valids++;
            }
            // 4.添加到_pendings中
            _pendings.push_back(msg);
            return true;
        }

实现步骤

  • 创建消息对象msg_ptr,并填充消息的基本属性。
  • 根据确定的qmode参数或者消息的基本属性决定该消息是否需要持久化。
  • 若消息为持久化消息,则将其存储到MessageMapper中,并更新_durables和计数。
  • 最后将消息添加到_pendings列表,表示该消息等待被推送。
获取消息 (front)

功能:获取队列中的第一条消息,将其从待推送中移除,添加到待确认队列中。

cpp 复制代码
       msg_ptr front() // 获取队头,方便后续推送给client
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_pendings.size() == 0)
            {
                ILOG("队列%s没有消息了", _qname.c_str());
                return msg_ptr();
            }
            // 1.获取_pendings队首消息
            // 2.头删
            // 3.添加到_wait_acks中
            msg_ptr msg = _pendings.front();
            _pendings.pop_front();
            _wait_acks.insert(std::make_pair(msg->msg_self().basic_attributes().id(), msg));
            return msg;
        }
确认消息 (remove)

功能:从待确认队列中删除消息,并根据情况删除持久化存储中的数据。

cpp 复制代码
    bool remove(const std::string &msg_id) // 确认消息
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 重复确认,remove
            if (_wait_acks.find(msg_id) == _wait_acks.end())
            {
                DLOG("重复确认消息: msg_id:%s is not in wait_acks", msg_id.c_str());
                return true;
            }
            // 1.若为持久化数据,则先删除持久化数据
            if (_wait_acks[msg_id]->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
            {
                _mapper.remove(_wait_acks[msg_id]);
                _durables.erase(msg_id);
                _valids--;
                // 删除持久化数据之后,才判断是否GC
                Gc();
            }
            // 2.删除_wait_acks对应的数据
            _wait_acks.erase(msg_id);
            return true;
        }

实现步骤

  • 锁定_mutex
  • 检查待确认消息队列_wait_acks中是否存在该消息。
  • 若消息为持久化消息,调用_mapper.remove从持久化存储中删除,_durables并移除该消息。
  • 更新有效消息数量_valids,并调用Gc进行垃圾回收(如果需要)。
  • 从中_wait_acks移除该消息。
清理队列 (clear)

功能:清空消息队列,删除持久化存储中的数据。

cpp 复制代码
  void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeFile();
            _pendings.clear();
            _durables.clear();
            _wait_acks.clear();
            _valids = _total = 0;
        }

实现步骤

  • 锁定_mutex
  • 调用_mapper.removeFile()删除持久化文件。
  • 清空_pendings_durables_wait_acks中的所有消息。
恢复消息 (recover)

功能:从持久化存储中恢复消息,用于系统重启或异常恢复时重新加载队列消息到内存中

cpp 复制代码
  void recover()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto recoveredMessages = _mapper.gc();
            if (recoveredMessages.empty())
            {
                ELOG("队列:%s恢复失败,没有合法的消息返回", _qname.c_str());
                return;
            }
            _pendings = recoveredMessages;
            for (auto &ptr : _pendings)
            {
                _durables.emplace(ptr->msg_self().basic_attributes().id(), ptr);
            }
            _total = _valids = _durables.size();
        }

实现步骤

  • 锁定_mutex
  • 调用_mapper.gc()恢复所有有效的持久化消息。
  • 将恢复的消息重新加入_pendings队列,并更新_durables
垃圾回收 (Gc)

功能 :当持久化文件中存在大量无效消息时,进行垃圾回收,删除文件中的无效消息,并更新内存中消息的状态。

cpp 复制代码
  bool checkGC()
        {
            if (_total > 2000 && _valids * 2 < _total)
                return true;
            return false;
        }
        void Gc()
        {
            if (!checkGC())
                return;
            // gc后返回所有合法的消息,更新内存中的数据
            auto new_list = _mapper.gc();
            for (auto ptr : new_list)
            {
                std::string msg_id = ptr->msg_self().basic_attributes().id();
                if (_durables.find(msg_id) == _durables.end())
                {
                    DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
                    // 持久化的消息没有在内存中被管理,则重新加入_pendings
                    _pendings.push_back(ptr);
                    _durables[msg_id] = ptr;
                    continue;
                }
                // 更新_durables
                _durables[msg_id] = ptr;
                // 更新_total
                _total = _valids = _durables.size();
            }
        }

实现步骤

  • 调用checkGC()判断是否满足GC条件(如持久化消息总数超过2000且有效消息低于总数的一半)。
  • 如果需要垃圾返回,则调用_mapper.gc()方法重新加载有效消息,并更新_pendings_durables

五.MessageManager的实现

MessageManager类旨在管理多个消息队列,通过提供队列初始化、消息插入、确认、获取等功能,帮助我们处理消息的持久化存储与队列操作。

主要思路是利用C++的std::unordered_map存储多个消息队列(QueueMessage),并通过互斥锁(std::mutex)来保证多线程环境下对这些队列的安全访问。此外,通过共享指针(std::shared_ptr)来管理消息队列对象的生命周期,避免手动管理内存带来的复杂性和潜在的内存泄漏问题,其中的接口是对QueueMessage的封装。

cpp 复制代码
    class QueueMessage
    {
        using msg_ptr = std::shared_ptr<msg::Message>;

    private:
        std::string _qname;    // 对应的队列名称
        std::mutex _mutex;     // 一个队列对应一把锁
        MessageMapper _mapper; // 持久化的管理句柄
        // 内存中管理消息的数据结构
        std::list<msg_ptr> _pendings;                        // 待推送消息
        std::unordered_map<std::string, msg_ptr> _durables;  // 持久化消息
        std::unordered_map<std::string, msg_ptr> _wait_acks; // 待确认消息
        // 持久化文件中的消息数量
        size_t _valids = 0; // 有效消息数量(针对持久化的消息,用来checkGc)
        size_t _total = 0;  // 总消息数量(针对持久化的消息,用来checkGc,文件中的msg数量)
    public:
        QueueMessage(std::string &dir, const std::string qname)
            : _qname(qname), _mapper(dir, qname)
        {
        }
        bool push(const msg::BasicAttributes *pbasic, const std::string &body, bool qmode) // 新增消息
        {
            // 1.创建消息,并填充
            msg_ptr msg = std::make_shared<msg::Message>();
            if (pbasic == nullptr)
            {
                msg::DeliverMode dmode = qmode == true ? msg::DeliverMode::DURABLE : msg::DeliverMode::UNDURABLE;
                msg->mutable_msg_self()->mutable_basic_attributes()->set_id(UUIDHelper::uuid());
                msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
                msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key("default");
            }
            else
            {
                msg::DeliverMode dmode = qmode == true ? pbasic->deliver_mode() : msg::DeliverMode::UNDURABLE;
                msg->mutable_msg_self()->mutable_basic_attributes()->set_id(pbasic->id());
                msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
                msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key(pbasic->routine_key());
            }
            // 2.填充body
            msg->mutable_msg_self()->set_body(body);

            std::unique_lock<std::mutex> lock(_mutex);
            // 3.若需要持久化,则额外添加到持久化对应的_durables和文件中,并继续填充管理字段
            if (msg->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
            {
                // 先填充好管理字段,再添加到_durables中
                msg->mutable_msg_self()->set_valid("1");
                bool ret = _mapper.insert(msg);
                if (!ret)
                {
                    DLOG("持久化存储消息:%s 失败了!", body.c_str());
                    return false;
                }
                _durables[msg->msg_self().basic_attributes().id()] = msg;

                _total++;
                _valids++;
            }
            // 4.添加到_pendings中
            _pendings.push_back(msg);
            return true;
        }
        msg_ptr front() // 获取队头,方便后续推送给client
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_pendings.size() == 0)
            {
                ILOG("队列%s没有消息了", _qname.c_str());
                return msg_ptr();
            }
            // 1.获取_pendings队首消息
            // 2.头删
            // 3.添加到_wait_acks中
            msg_ptr msg = _pendings.front();
            _pendings.pop_front();
            _wait_acks.insert(std::make_pair(msg->msg_self().basic_attributes().id(), msg));
            return msg;
        }
        bool remove(const std::string &msg_id) // 确认消息
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 重复确认,remove
            if (_wait_acks.find(msg_id) == _wait_acks.end())
            {
                DLOG("重复确认消息: msg_id:%s is not in wait_acks", msg_id.c_str());
                return true;
            }
            // 1.若为持久化数据,则先删除持久化数据
            if (_wait_acks[msg_id]->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
            {
                _mapper.remove(_wait_acks[msg_id]);
                _durables.erase(msg_id);
                _valids--;
                // 删除持久化数据之后,才判断是否GC
                Gc();
            }
            // 2.删除_wait_acks对应的数据
            _wait_acks.erase(msg_id);
            return true;
        }
        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeFile();
            _pendings.clear();
            _durables.clear();
            _wait_acks.clear();
            _valids = _total = 0;
        }
        size_t validNum()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _valids;
        }
        size_t totalNum()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _total;
        }
        size_t waitAckNum()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _wait_acks.size();
        }
        size_t pendingNum()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _pendings.size();
        }

        void recover()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto recoveredMessages = _mapper.gc();
            if (recoveredMessages.empty())
            {
                ELOG("队列:%s恢复失败,没有合法的消息返回", _qname.c_str());
                return;
            }
            _pendings = recoveredMessages;
            for (auto &ptr : _pendings)
            {
                //_durables.insert(std::make_pair(ptr->msg_self().basic_attributes().id(), ptr));
                _durables.emplace(ptr->msg_self().basic_attributes().id(), ptr);
                // ILOG("recover id: %s",ptr->msg_self().basic_attributes().id().c_str());
                // ILOG("第%d条消息",ptr->msg_self().msg_id())")
            }
            _total = _valids = _durables.size();
        }

    private:
        bool checkGC()
        {
            if (_total > 2000 && _valids * 2 < _total)
                return true;
            return false;
        }
        void Gc()
        {
            if (!checkGC())
                return;
            auto new_list = _mapper.gc();
            if (new_list.empty())
            {
                ELOG("垃圾回收后没有合法的消息返回");
                return;
            }
            // gc后返回所有合法的消息,再去更新内存中的数据
            for (auto ptr : new_list)
            {
                std::string msg_id = ptr->msg_self().basic_attributes().id();
                if (_durables.find(msg_id) == _durables.end()) // 一般不会出现这种情况
                {
                    DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
                    // 持久化的消息没有在内存中被管理,则重新加入_pendings
                    _pendings.push_back(ptr);
                    _durables[msg_id] = ptr;
                    continue;
                }
                // 更新_durables,实际更新的是 存储位置和长度
                _durables[msg_id]->set_offset(ptr->offset());
                _durables[msg_id]->set_length(ptr->length());
            }
            // 更新_total和_valid
            _total = _valids = _durables.size();
            // std::cout << "Gc后_total:%d, _valids:%d, _durables.size:%d";
        }
    };

    class MessageManager
    {

        using ptr = std::shared_ptr<QueueMessage>;
        using msg_ptr = std::shared_ptr<msg::Message>;

    public:
        using mmp = std::shared_ptr<MessageManager>;

    private:
        std::mutex _mutex;
        std::string _dir;
        std::unordered_map<std::string, ptr> _msgs;

    public:
        MessageManager(const std::string &dir)
            : _dir(dir) {}

        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            for (auto &kv : _msgs)
            {
                kv.second->clear();
            }
            _msgs.clear();
        }
        void initQueueMsg(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it != _msgs.end())
                {
                    ILOG("队列%s已经存在,无需init", qname.c_str());
                    return;
                }
                _msgs.insert(std::make_pair(qname, std::make_shared<QueueMessage>(_dir, qname)));
                tmp = _msgs[qname];
            }
            _msgs[qname]->recover(); // 恢复持久化数据,用QueueMessage自己的mutex即可
        }
        void destroyQueueMsg(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,destroyQueueMsg fail", qname.c_str());
                    return;
                }
                tmp = it->second;
                _msgs.erase(it);
            }
            tmp->clear();
        }
        bool insertMsg(const std::string &qname, msg::BasicAttributes *bp, const std::string &body, bool mode)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法insertMsg", qname.c_str());
                    return false;
                }
                tmp = it->second;
            }
            return tmp->push(bp, body, mode);
        }
        void ack(const std::string &qname, const std::string &id)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法ack", qname.c_str());
                    return;
                }
                tmp = it->second;
            }
            tmp->remove(id);
        }
        msg_ptr front(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取队首元素", qname.c_str());
                    return msg_ptr();
                }
                tmp = it->second;
            }
            return tmp->front();
        }
        size_t validNum(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取validNum", qname.c_str());
                    return -1;
                }
                tmp = it->second;
            }
            // 减少锁冲突,MessageManager的临界区越小越好
            return tmp->validNum();
        }
        size_t totalNum(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取totalNum", qname.c_str());
                    return -1;
                }
                tmp = it->second;
            }
            return tmp->totalNum();
        }
        size_t waitAckNum(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取waitAckNum", qname.c_str());
                    return -1;
                }
                tmp = it->second;
            }
            return tmp->waitAckNum();
        }
        size_t pendingNum(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取pendingNum", qname.c_str());
                    return -1;
                }
                tmp = it->second;
            }
            return tmp->pendingNum();
        }
    };

六.message.hpp全部代码

cpp 复制代码
#pragma once

#include "../common_mq/helper.hpp"
#include "../common_mq/logger.hpp"
#include "../common_mq/msg.pb.h"
#include <string>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <cassert>
#include <cstring>
#include <list>

// 消息持久化映射类,用文件而不是数据库
namespace mq
{
    class MessageMapper
    {
        using msg_ptr = std::shared_ptr<msg::Message>;
        const std::string dataSuf = ".data";
        const std::string tmpSuf = ".tmp";

    private:
        std::string _qname;    // 队列名
        std::string _dataFile; // 存储数据的文件名
        std::string _tmpFile;  // 临时文件名
    public:
        MessageMapper(std::string &dir, const std::string qname)
            : _qname(qname)
        {
            if (dir.back() != '/')
                dir.append("/");
            // 创建目录
            if (!FileHelper::createDir(dir))
            {
                ELOG("create dir %s failed", dir.c_str());
                assert(0);
            }
            _dataFile = dir + _qname + dataSuf;
            _tmpFile = dir + _qname + tmpSuf;

            createFile();
        }
        // 1.创建/删除msg数据文件
        bool createFile()
        {
            return FileHelper::createFile(_dataFile);
        }
        void removeFile()
        {
            FileHelper::removeFile(_dataFile);
            FileHelper::removeFile(_tmpFile);
        }
        // 2.插入/删除msg数据
        bool insert(msg_ptr &msg)
        {
            return _insert(_dataFile, msg);
        }
        bool remove(msg_ptr &msg)
        {
            // 常规情况下,修改msg和文件中的valid为"0"即可
            // 必要时进行gc垃圾回收
            // 1.修改msg中的
            msg->mutable_msg_self()->set_valid("0");
            // 2.覆盖式修改文件中的内容
            size_t offset = msg->offset();
            std::string str = msg->msg_self().SerializeAsString();
            if (str.size() != msg->length())
            {
                ELOG("msg length is not equal to msg_self length");
                return false;
            }
            FileHelper helper(_dataFile);
            if (!helper.write(str.c_str(), offset, str.size()))
            {
                ELOG("%s:write msg failed", _dataFile.c_str());
                return false;
            }
            return true;
        }
        // 3.recover和gc相关
        std::list<msg_ptr> gc()
        {
            std::list<msg_ptr> ret;
            // 1. 从文件中恢复所有有效数据
            if (!load(ret))
            {
                ELOG("recover failed");
                return ret;
            }
            // 2. 打开tmp文件,遍历有效数据,创建并写入tmp文件中
            FileHelper::createFile(_tmpFile);
            bool check = true;
            for (auto &pmsg : ret)
            {
                check = _insert(_tmpFile, pmsg);
                if (check == false)
                {
                    DLOG("向临时文件写入消息数据失败!!");
                    return ret;
                }
            }
            // 3. 删除原来的data文件
            check = FileHelper::removeFile(_dataFile);
            if (!check)
            {
                ELOG("删除原数据文件失败!!");
                return ret;
            }
            // 4. 重命名tmp文件为data文件
            FileHelper helper(_tmpFile);
            check = helper.rename(_dataFile);
            if (!check)
            {
                ELOG("重命名临时文件失败!!");
                return ret;
            }

            return ret;
        }
        bool load(std::list<msg_ptr> &ret) // 将文件中的内容加载到内存的list中
        {
            FileHelper helper(_dataFile);
            size_t offset = 0;
            size_t total_size = helper.size();
          //  ILOG("total_size: %ld", total_size);
            while (offset < total_size)
            {
                // 1. 读取size_t字节长度
                std::string len_str;
                len_str.resize(sizeof(size_t));
                if (!helper.read(&len_str[0], offset, sizeof(size_t)))
                {
                    ELOG("%s:read msg length failed", _dataFile.c_str());
                    return false;
                }
                // 2. 读取msg_self内容
                size_t len = 0;
                std::memcpy(&len, len_str.data(), sizeof(size_t));
                ILOG("len: %zu", len);

                std::string msg_str;
                msg_str.resize(len);
                if (!helper.read(&msg_str[0], offset + sizeof(size_t), len))
                {
                    ELOG("%s:read msg failed", _dataFile.c_str());
                    return false;
                }
                offset += sizeof(size_t) + len;
                auto msg_ptr = std::make_shared<msg::Message>();
                msg_ptr->mutable_msg_self()->ParseFromString(msg_str);
                // 文件中的无效信息不会恢复
                if (msg_ptr->msg_self().valid() == "0") // 无效消息
                {
                    DLOG("msg is invalid");
                    continue;
                }
                ret.push_back(msg_ptr);
            }

            return true;
        }
        // 子函数
    private:
        bool _insert(const std::string &name, msg_ptr &msg)
        {
            // a.将msg内部的MessageSelf序列化
            // b.将字符串写入文件末尾,先写入size_t字节长度,再写string内容
            // c.填充引用的msg的相关管理字段,使其在内存中可以管理文件内容
            std::string msg_str = msg->msg_self().SerializeAsString();
            FileHelper helper(name);
            size_t offset = helper.size();
            size_t start = offset;
            size_t len = msg_str.size();
            // 先写入size_t字节长度
            if (!helper.write((char *)&len, offset, sizeof(size_t)))
            {
                ELOG("%s:write msg length failed", name.c_str());
                return false;
            }
            offset += sizeof(len);
            // 再写入序列化后的字符串msg_str
            if (!helper.write(msg_str.c_str(), offset, len))
            {
                ELOG("%s:write msg failed", name.c_str());
                return false;
            }
            offset += len;
            msg->set_length(len);
            msg->set_offset(start + sizeof(size_t));
            return true;
        }
    };

    class QueueMessage
    {
        using msg_ptr = std::shared_ptr<msg::Message>;

    private:
        std::string _qname;    // 对应的队列名称
        MessageMapper _mapper; // 持久化的管理句柄
        // 内存中管理消息的数据结构
        std::list<msg_ptr> _pendings;                        // 待推送消息
        std::unordered_map<std::string, msg_ptr> _durables;  // 持久化消息
        std::unordered_map<std::string, msg_ptr> _wait_acks; // 待确认消息
        // 持久化文件中的消息数量
        size_t _valids = 0; // 有效消息数量(针对持久化的消息,用来checkGc)
        size_t _total = 0;  // 总消息数量(针对持久化的消息,用来checkGc,文件中的msg数量)
        std::mutex _mutex;  // 一个队列对应一把锁
    public:
    public:
        QueueMessage(std::string &dir, const std::string qname)
            : _qname(qname), _mapper(dir, qname)
        {
        }
        bool push(const msg::BasicAttributes *pbasic, const std::string &body, bool qmode) // 新增消息
        {
            // 1.创建消息,并填充
            msg_ptr msg = std::make_shared<msg::Message>();
            if (pbasic == nullptr)
            {
                msg::DeliverMode dmode = qmode == true ? msg::DeliverMode::DURABLE : msg::DeliverMode::UNDURABLE;
                msg->mutable_msg_self()->mutable_basic_attributes()->set_id(UUIDHelper::uuid());
                msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
                msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key("default");
            }
            else
            {
                msg::DeliverMode dmode = qmode == true ? pbasic->deliver_mode() : msg::DeliverMode::UNDURABLE;
                msg->mutable_msg_self()->mutable_basic_attributes()->set_id(pbasic->id());
                msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
                msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key(pbasic->routine_key());
            }
            // 2.填充body
            msg->mutable_msg_self()->set_body(body);

            std::unique_lock<std::mutex> lock(_mutex);
            // 3.若需要持久化,则额外添加到持久化对应的_durables和文件中,并继续填充管理字段
            if (msg->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
            {
                msg->mutable_msg_self()->set_valid("1");
                bool ret = _mapper.insert(msg); // 先填充好管理字段,再添加到_durables中
                if (!ret)
                {
                    DLOG("持久化存储消息:%s 失败了!", body.c_str());
                    return false;
                }
                _durables[msg->msg_self().basic_attributes().id()] = msg;

                _total++;
                _valids++;
            }
            // 4.添加到_pendings中
            _pendings.push_back(msg);
            return true;
        }
        msg_ptr front() // 获取队头,方便后续推送给client
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_pendings.size() == 0)
            {
                ILOG("队列%s没有消息了", _qname.c_str());
                return msg_ptr();
            }
            // 1.获取_pendings队首消息
            // 2.头删
            // 3.添加到_wait_acks中
            msg_ptr msg = _pendings.front();
            _pendings.pop_front();
            _wait_acks.insert(std::make_pair(msg->msg_self().basic_attributes().id(), msg));
            return msg;
        }
        bool remove(const std::string &msg_id) // 确认消息
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 重复确认,remove
            if (_wait_acks.find(msg_id) == _wait_acks.end())
            {
                DLOG("重复确认消息: msg_id:%s is not in wait_acks", msg_id.c_str());
                return true;
            }
            // 1.若为持久化数据,则先删除持久化数据
            if (_wait_acks[msg_id]->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
            {
                _mapper.remove(_wait_acks[msg_id]);
                _durables.erase(msg_id);
                _valids--;
                // 删除持久化数据之后,才判断是否GC
                Gc();
            }
            // 2.删除_wait_acks对应的数据
            _wait_acks.erase(msg_id);
            return true;
        }
        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeFile();
            _pendings.clear();
            _durables.clear();
            _wait_acks.clear();
            _valids = _total = 0;
        }
        size_t validNum()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _valids;
        }
        size_t totalNum()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _total;
        }
        size_t waitAckNum()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _wait_acks.size();
        }
        size_t pendingNum()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _pendings.size();
        }

        void recover()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _pendings = _mapper.gc();
            for (auto &ptr : _pendings)
            {
                _durables.insert(std::make_pair(ptr->msg_self().basic_attributes().id(), ptr));
                // ILOG("recover id: %s",ptr->msg_self().basic_attributes().id().c_str());
                // ILOG("第%d条消息",ptr->msg_self().msg_id())")
            }
            _total = _valids = _durables.size();
        }

    private:
        bool checkGC()
        {
            if (_total > 2000 && _valids * 2 < _total)
                return true;
            return false;
        }
        void Gc()
        {
            if (!checkGC())
                return;
            // gc后返回所有合法的消息,更新内存中的数据
            auto new_list = _mapper.gc();
            for (auto ptr : new_list)
            {
                std::string msg_id = ptr->msg_self().basic_attributes().id();
                if (_durables.find(msg_id) == _durables.end())
                {
                    DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
                    // 持久化的消息没有在内存中被管理,则重新加入_pendings
                    _pendings.push_back(ptr);
                    _durables[msg_id] = ptr;
                    continue;
                }
                // 更新_durables
                _durables[msg_id] = ptr;
                // 更新_total
                _total = _valids = _durables.size();

               // std::cout << "Gc后_total:%d, _valids:%d, _durables.size:%d";
            }
        }
    };

    class MessageManager
    {

        using ptr = std::shared_ptr<QueueMessage>;
        using msg_ptr = std::shared_ptr<msg::Message>;

    public:
        using mmp = std::shared_ptr<MessageManager>;

    private:
        std::mutex _mutex;
        std::string _dir;
        std::unordered_map<std::string, ptr> _msgs;

    public:
        MessageManager(const std::string &dir)
            : _dir(dir) {}

        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            for (auto &kv : _msgs)
            {
                kv.second->clear();
            }
            _msgs.clear();
        }
        void initQueueMsg(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it != _msgs.end())
                {
                    ILOG("队列%s已经存在,无需init", qname.c_str());
                    return;
                }
                _msgs.insert(std::make_pair(qname, std::make_shared<QueueMessage>(_dir, qname)));
                tmp = _msgs[qname];
            }
            _msgs[qname]->recover(); // 恢复持久化数据,用QueueMessage自己的mutex即可
        }
        void destroyQueueMsg(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,destroyQueueMsg fail", qname.c_str());
                    return;
                }
                tmp = it->second;
                _msgs.erase(it);
            }
            tmp->clear();
        }
        bool insertMsg(const std::string &qname, msg::BasicAttributes *bp, const std::string &body, bool mode)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法insertMsg", qname.c_str());
                    return false;
                }
                tmp = it->second;
            }
            return tmp->push(bp, body, mode);
        }
        void ack(const std::string &qname, const std::string &id)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法ack", qname.c_str());
                    return;
                }
                tmp = it->second;
            }
            tmp->remove(id);
        }
        msg_ptr front(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取队首元素", qname.c_str());
                    return msg_ptr(); 
                }
                tmp = it->second;
            }
            return tmp->front();
        }
        size_t validNum(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取validNum", qname.c_str());
                    return -1;
                }
                tmp = it->second;
            }
            // 减少锁冲突,MessageManager的临界区越小越好
            return tmp->validNum();
        }
        size_t totalNum(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取totalNum", qname.c_str());
                    return -1;
                }
                tmp = it->second;
            }
            return tmp->totalNum();
        }
        size_t waitAckNum(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取waitAckNum", qname.c_str());
                    return -1;
                }
                tmp = it->second;
            }
            return tmp->waitAckNum();
        }
        size_t pendingNum(const std::string &qname)
        {
            ptr tmp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msgs.find(qname);
                if (it == _msgs.end())
                {
                    ELOG("队列%s不存在,无法获取pendingNum", qname.c_str());
                    return -1;
                }
                tmp = it->second;
            }
            return tmp->pendingNum();
        }
    };
};
相关推荐
火山口车神丶8 分钟前
某车企ASW面试笔试题
c++·matlab
hzyyyyyyyu10 分钟前
内网安全隧道搭建-ngrok-frp-nps-sapp
服务器·网络·安全
菠萝咕噜肉i11 分钟前
超详细:Redis分布式锁
数据库·redis·分布式·缓存·分布式锁
长风清留扬13 分钟前
一篇文章了解何为 “大数据治理“ 理论与实践
大数据·数据库·面试·数据治理
明明跟你说过25 分钟前
Linux中的【tcpdump】:深入介绍与实战使用
linux·运维·测试工具·tcpdump
OpsEye26 分钟前
MySQL 8.0.40版本自动升级异常的预警提示
数据库·mysql·数据库升级
Ljw...26 分钟前
表的增删改查(MySQL)
数据库·后端·mysql·表的增删查改
是阿建吖!33 分钟前
【优选算法】二分查找
c++·算法
刽子手发艺37 分钟前
WebSocket详解、WebSocket入门案例
网络·websocket·网络协议