仿RabbitMq实现消息队列正式篇(虚拟机篇)

@TOC

目录

虚拟机模块

要管理的数据

要管理的操作

消息管理模块

要管理的数据

消息信息

消息主体

消息的管理

管理方法

管理数据

管理操作

队列消息管理

交换机数据管理

要管理的数据

要管理的操作

代码展示

队列数据管理

要管理的数据

要管理的操作

代码展示

绑定数据管理模块

管理的数据

管理的操作

代码展示

虚拟机代码展示


虚拟机模块

要管理的数据

  • 交换机数据管理句柄
  • 队列数据管理句柄
  • 绑定信息数据管理模块
  • 消息数据管理模块

要管理的操作

  • 声明 / 删除交换机:在删除交换机的时候,要删除相关的绑定信息
  • 声明 / 删除队列 :在删除队列的时候,要删除相关的绑定信息以及消息数据
  • 队列的绑定 / 解绑 :绑定的时候,必须交换机和队列是存在的
  • 获取指定队列的消息
  • 对指定队列的指定消息进行确认
  • 获取交换机相关的所有绑定信息:一条消息要发布给指定交换机的时候,交换机获取所有绑定信息,来确认消息要发布到哪个队列

基于这个,那我们先完成虚拟机模块管理的相关数据

消息管理模块

syntax = "proto3";  
package mymq;

enum ExchangeType  # 交换类型
{
    UNKNOWTYPE = 0;
    DIRECT = 1;
    FANOUT = 2;
    TOPIC = 3; 
};

enum DeliveryMode # 是否持久化
{
    UNKNOWMODE = 0;
    UNDURABLE = 1;
    DURABLE = 2;
};
 
message BasicProperties #消息的属性
{
    string id = 1;                         # 消息ID, UUID
    DeliveryMode delivery_mode = 2;        # 持久化模块
    string rounting_key = 3;               # routing_key绑定信息
};

message Message
{
    message Payload
    {
        BasicProperties properties = 1;     # 消息属性
        string body = 2;                    # 有效载荷数据
        string valid = 3;                   # 消息是否有效
    };
    Payload payload = 1;                    # 真正持久化的只有这一个字段
    uint32 offset = 2;                      # 消息的位置
    uint32 length = 3;                      # 消息的长度
};

要管理的数据

消息信息

  1. ID:消息的唯一标识
  2. 持久化标识:表示是否对消息进行持久化(还取决于队列的持久化标志)
  3. routing_key:决定了当前消息要发布的队列(消息发布到交换机后,根据绑定队列binding_key决定是否发布到指定队列)
消息主体
  1. 存储偏移量:消息以队列为单元存储在文件中,这个偏移量,是当前消息相对于文件起始位置的偏移量
  2. 消息长度:从偏移量位置取出指定长度的消息(解决了粘包问题)
  3. 是否有效标志:标识当前消息是否已经删除(删除一条消息,并不是用后面的消息覆盖掉前面的数据,而是重置有效标志位,当一个文件中的有效消息占据总消息比例不到50%,且数据量超过2000,则进行垃圾回收,重新整理文件数据存储)

        当系统重启时,也需要重新加载有效消息即可。

消息的管理

管理方法

以队列为单元进行管理(因为消息的所有操作都是以队列为单元)

管理数据
  1.  消息链表
  2. 待确定消息hash
  3. 持久化消息hash
  4. 持久化的有效消息数据
  5. 持久化的总的消息数据
管理操作
  • 向队列新增消息
  • 获取队首消息
  • 对消息进行确认
  • 恢复队列历史消息
  • 垃圾回收
  • 删除队列相关消息文件
队列消息管理
  • 初始化队列消息结构
  • 移除队列消息结果
  • 向队列新增消息
  • 对队列消息进行确认
  • 恢复队列历史消息
#ifndef __M_MSG_H__
#define __M_MSG_H__

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

namespace mymq
{

    // 消息的持久化管理
    //  a. 管理数据
    //  i. 队列消息⽂件存储的路径
    //  ii. 队列消息的存储⽂件名
    //  iii. 队列消息的临时交换⽂件名
    // b. 管理操作
    // i. ⽇志消息存储在⽂件中(4B⻓度+(属性+内容+有效位)序列化消息,连续存储即可)
    // ii. 提供队列消息⽂件创建/删除功能
    // iii. 提供队列消息的新增持久化/删除持久化
    // iv. 提供持久化内容的垃圾回收(其实就是重新加载出所有有效消息返回,并重新⽣成新的消息
    // 存储⽂件)
#define DATAFILE_SUBFIX ".mqd"
#define TMPFILE_SUBFIX ".mqb.tmp"
    using MessagePtr = std::shared_ptr<mymq::Message>;

    // 消息持久化
    class MessageMapper
    {
    public:
        MessageMapper(std::string &basedir, const std::string &qname)
            : _qname(qname)
        {
            if (basedir.back() != '/')
            {
                basedir.push_back('/');
            }
            _datafile = basedir + qname + DATAFILE_SUBFIX;
            _tmpfile = basedir + qname + TMPFILE_SUBFIX;
            if (FileHelper(basedir).exists() == false)
            {
                FileHelper::createDirectory(basedir);
            }
            createMsgFile();
        }

        bool createMsgFile()
        {
            if (FileHelper(_datafile).exists() == true)
            {
                return true;
            }
            bool ret = FileHelper::createFile(_datafile);
            if (ret == false)
            {
                ELOG("创建队列数据文件:%s 失败", _datafile.c_str());
                return false;
            }
            return true;
        }

        void removeMsgFile()
        {
            FileHelper::removeFile(_datafile);
            FileHelper::removeFile(_tmpfile);
        }

        bool insert(MessagePtr &msg)
        {
            return insert(_datafile, msg);
        }

        bool remove(MessagePtr &msg)
        {
            // 1. 将msg中的有效标志位修改为‘0’
            msg->mutable_payload()->set_valid("0");
            // 2,对msg进行序列化
            std::string body = msg->SerializeAsString();
            if (body.size() != msg->length())
            {
                ELOG("不能修改文件中的数据信息,因为生成的数据与原数据长度不一样");
                return false;
            }
            // 3. 将序列化后的消息,写入到数据在文件中的指定位置(覆盖原有的数据)
            FileHelper helper(_datafile);
            bool ret = helper.write(body.c_str(), msg->offset(), body.size());
            if (ret == false)
            {
                DLOG("向队列数据文件写入失败!");
                return false;
            }
            return true;
        }

        std::list<MessagePtr> gc()
        {
            std::list<MessagePtr> result;
            bool ret = load(result);
            if (ret == false)
            {
                ELOG("加载有效数据失败\n");
                return result;
            }
            // 2. 将有效数据,进行序列化存储到临时文件中
            FileHelper::createFile(_tmpfile);
            for (auto e : result)
            {
                DLOG("向临时文件中添加数据 : %s", e->payload().body().c_str());
                ret = insert(_tmpfile, e);
                if (ret == false)
                {
                    ELOG("向临时文件中添加数据失败");
                    return result;
                }
            }
            // 3. 删除源文件
            ret = FileHelper::removeFile(_datafile);
            if (ret == false)
            {
                ELOG("删除原文件失败");
                return result;
            }
            // 4. 修改临时文件的名字,改成原文件的名称
            ret = FileHelper(_tmpfile).rename(_datafile.c_str());
            if (ret == false)
            {
                ELOG("修改文件名称失败");
                return result;
            }
            // 5. 返回新的有效数据
            return result;
        }

    private:
        bool load(std::list<MessagePtr> &result)
        {
            // 1. 加载出文件中的所有的有效数据:存储格式:4字节长度 | 数据 | 4字节长度 | 数据 ......
            FileHelper data_file_helper(_datafile);
            size_t offset = 0;   // 用来定位
            size_t msg_size = 0; // 用来 4 字节长度的数据长度
            size_t file_size = data_file_helper.size();
            while (offset < file_size)
            {
                bool ret = data_file_helper.read((char*)&msg_size, offset, sizeof(size_t));
                if (ret == false)
                {
                    DLOG("读取消息长度失败!");
                }
                offset += sizeof(size_t);
                std::string msg_body(msg_size, '\0');
                ret = data_file_helper.read(&msg_body[0], offset, msg_size);
                // 为什么使用 &msg_body[0]
                // &msg_body[0] 返回的是指向 std::string 内部字符数组首元素的指针,即指向字符串内容的指针。
                // 适用于需要直接操作字符串内容的场景,例如文件读写操作

                // &msg_body:
                // &msg_body 返回的是 std::string 对象本身的指针,即指向 std::string 对象的指针。
                // 不适用于直接操作字符串内容的场景,而是用于操作整个 std::string 对象本身。
                if (ret == false)
                {
                    DLOG("读取消息数据失败");
                    return false;
                }
                offset += msg_size;
                MessagePtr msgp = std::make_shared<Message>();
                msgp->mutable_payload()->ParseFromString(msg_body);

                // 如果是无效消息,则直接处理下一个
                if (msgp->payload().valid() == "0")
                {
                    ELOG("加载到无效消息:%s", msgp->payload().body().c_str());
                    continue;
                }
                // 有效消息则保存下来
                result.push_back(msgp);
            }
            return true;
        }

        bool insert(const std::string &filename, MessagePtr &msg)
        {
            // 新增数据都是添加在文件末尾的
            //  1. 进行消息的序列化,获取到格式化后的消息
            // 因为使用的是 sqlite 数据库,存储的是二进制信息,所以需要进行序列化
            std::string body = msg->payload().SerializeAsString();
            // 2. 获取文件长度
            FileHelper helper(filename);
            size_t fsize = helper.size();
            size_t msg_size = body.size();
            // 写入逻辑:1. 先写入 4 字节数据长度,2. 再写入指定长度数据
            bool ret = helper.write((char *)&msg_size, fsize, sizeof(size_t));
            if (ret == false)
            {
                DLOG("向队列数据文件写入数据长度失败!");
                return false;
            }
            // 将数据写入文件的指定位置
            ret = helper.write(body.c_str(), fsize + sizeof(size_t), msg_size);
            if (ret == false)
            {
                DLOG("向队列数据文件写入数据失败!");
                return false;
            }
            // 更新msg中的实际存储信息
            msg->set_offset(fsize + sizeof(size_t));
            msg->set_length(msg_size);
            return true;
        }

    private:
        std::string _qname;
        std::string _datafile;
        std::string _tmpfile;
    };

    // 管理数据
    class QueueMessage
    {
    public:
        using ptr = std::shared_ptr<QueueMessage>;
        // 初始化
        QueueMessage(std::string &basedir, const std::string &qname)
            : _mapper(basedir, qname),
              _qname(qname),
              _valid_count(0),
              _total_count(0)
        {}

        bool recovery()
        {
            // 恢复历史消息
            std::unique_lock<std::mutex> lock(_mutex);
            _msgs = _mapper.gc();
            for (auto &msg : _msgs)
            {
                _durable_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
            }
            _valid_count = _total_count = _msgs.size();
            return true;
        }

        bool insert(const BasicProperties *bp, const std::string &body, bool queue_is_durable)
        {
            // 构造消息对象
            MessagePtr msg = std::make_shared<Message>();
            msg->mutable_payload()->set_body(body);
            if (bp != nullptr)
            {
                DeliveryMode mode = queue_is_durable ? bp->delivery_mode() : DeliveryMode::UNDURABLE;
                msg->mutable_payload()->mutable_properties()->set_id(bp->id());
                msg->mutable_payload()->mutable_properties()->set_delivery_mode(mode);
                msg->mutable_payload()->mutable_properties()->set_rounting_key(bp->rounting_key());
            }
            else
            {
                DeliveryMode mode = queue_is_durable ? DeliveryMode::DURABLE : DeliveryMode::UNDURABLE;
                msg->mutable_payload()->
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值