message.hpp消息管理模块

目录

一.Message模块介绍

二.Message的定义

1. DeliverMode 枚举

2. BasicAttributes 消息属性

3. Message 消息

3.1 MessageSelf 内部消息结构

3.2 Message 结构

三.MessageMapper的实现

类的主要功能与设计思路

成员变量

代码细节解析

1. 构造函数

2. 插入消息

3. 删除消息

4. 垃圾回收gc

5. 恢复数据recover

四.QueueMessage的实现

设计思路

成员变量

代码细节解析

构造函数

消息推送 (push)

获取消息 (front)

确认消息 (remove)

清理队列 (clear)

恢复消息 (recover)

垃圾回收 (Gc)

五.MessageManager的实现

六.message.hpp全部代码

一.Message模块介绍

由于消息数据需要在网络传输中处理,使用protobuf作为消息类型的定义工具。
protobuf 提供了内置的序列化和反序列化功能,使得消息的编解码操作更加简便。

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

为了更加高效地管理消息数据,我们以队列为单位进行管理
每个队列都拥有自己独立的数据存储文件和内存管理单元。


二.Message的定义

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

1. DeliverMode 枚举

enum DeliverMode
{
    NORMAL = 0;
    UNDURABLE = 1;
    DURABLE = 2;
}

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

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

2. BasicAttributes 消息属性

message BasicAttributes
{
    string id = 1;
    DeliverMode deliver_mode = 2;
    string routine_key = 3;
}

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

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

3. Message 消息

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

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. 构造函数
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. 插入消息
bool insert(msg_ptr &msg)
{
    return _insert(_dataFile, msg);
}

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

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. 删除消息

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

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
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
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:这两个变量分别记录持久化消息的有效数量和总数量,用于判断是否需要进行垃圾回收。

代码细节解析

构造函数
 QueueMessage(std::string &dir, const std::string qname)
            : _qname(qname), _mapper(dir, qname)
        {
        }

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

消息推送 (push)

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

  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)

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

       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)

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

    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)

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

  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)

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

  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)

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

  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的封装。

    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全部代码

#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();
        }
    };
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值