rabbitmq----数据管理模块

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


数据管理模块需要管理四种数据,分别是交换机数据管理/队列数据管理/绑定关系数据管理/队列消息数据管理。

交换机数据管理

管理的字段

需要管理的数据有下面这个5个

交换机名称:交换机的唯一标识

交换机类型:交换机有三种类型,直接交换/广播交换/主题交换。决定了消息的转发方式。

持久化标识:决定了交换机信息是否持久化存储。方便断电后恢复。

剩下的俩个字段不需要关心,是为了以后进行扩展的。

cpp 复制代码
 struct Exchange
{
	using ptr = std::shared_ptr<Exchange>;
	std::string name;                                  // 交换机名字
	ExchangeType type;                                 // 交换机类型
	bool durable;                                      // 交换机持久化标志位
	bool auto_delete;                                  // 交换机自动删除标志位 (还未实现该功能)
	google::protobuf::Map<std::string, std::string> args; // 其他参数 (方便以后扩展)
}

持久化管理类

交换机的信息提供了持久化管理的操作,我们使用sqlite进行存储。

要管理一个sqlite的操作句柄,这个句柄对象也是我们封装了一下sqlite的操作。

在构造函数种需要传入一个文件路径,也就是存储交换机信息的文件。

sqlite是一个本地化的数据库,不需要通过网络客户端服务器的模式来进行通信。本地提供一个文件就可以进行存储。一个文件就相当于一个数据库database,可以在这个数据库种创建多个表。

我们的交换机/队列/绑定关系信息的数据都是存储在这个文件种的.

cpp 复制代码
class ExchangeMapper
{
private:
    SqliteHelper _sql_helper;	//sqlite句柄
public:
     ExchangeMapper(const std::string &dbfile)
        : _sql_helper(dbfile)
    {
        // 创建父级目录
        const std::string path = FileHelper::parentDirectory(dbfile);
        FileHelper::createDirectory(path);

        // 创建/打开数据库文件
        assert(_sql_helper.open());

        // 创建交换机数据表
        createTable();
    }
}

这就是创建的交换机表。

cpp 复制代码
#define CREATE_TABLE "create table if not exists exchange_table(\
                                    name varchar(32) primary key, \
                                    type int, \
                                    durable int, \
                                    auto_delete int, \
                                    args varchar(128));"
bool ret = _sql_helper.exec(CREATE_TABLE, nullptr, nullptr);
if (ret == false)
{
   DLOG("创建交换机数据库表失败!!");
   abort(); // 直接异常退出程序
}

这个类还提供一个恢复的接口,他会查询交换机表中的所有记录,存放到一个哈希表中。

cpp 复制代码
//返回交换机表中所有数据,用于重启后恢复
std::unordered_map<std::string, Exchange::ptr> recovery(){

	std::unordered_map<std::string, Exchange::ptr> res;
	std::string sql = "select name, type, durable, auto_delete, args from exchange_table";
	_sql_helper.exec(sql, selectCallBack, &res);

	return res;	
}

内存管理类

在内存管理类中包含了交换机持久化管理类的对象,和一个哈希表,用来管理已经存在交换机信息。

在他的构造函数中调用了持久化管理的数据恢复接口,他会查询数据库表中所有的字段,返回一个哈希表,也就完成了交换机数据恢复。

cpp 复制代码
//交换机数据内存管理类,这个类才是对外提供的
class ExchangeManager
{
private:
    std::mutex _mutex;  //这个类对象可能被多线程访问,我们要加锁
    ExchangeMapper _mapper;
    std::unordered_map<std::string,Exchange::ptr> _Exchanges;   //管理已经存在的交换机
  ExchangeManager(const std::string &dbfile)
          :_mapper(dbfile)
   {
       //恢复交换机
       _Exchanges = _mapper.recovery();
   }

申明交换机

在rabbitMQ中不叫创建交换机,而是叫做申明交换机,它是一种强断言的思想,代表着存在及ok,不存在就创建。这个操作也很简单。

先看看哈希表中存不存在,存在就返回true,不存在就构建一个交换机对象,插入哈希表

cpp 复制代码
bool declareExchange(const std::string &name,
            ExchangeType type, bool durable, bool auto_delete,
            const google::protobuf::Map<std::string, std::string> &args)
	{
	std::unique_lock<std::mutex> lock(_mutex);
	auto it = _Exchanges.find(name);
	if(it != _Exchanges.end()){
	   //存在直接return
	   return true;
	}
	
	//定义一个Exchange对象
	auto ecp = std::make_shared<Exchange>(name,type,durable,auto_delete,args);
	//插入进数据库
	if(durable == true) {
	   bool ret = _mapper.insert(ecp);
	   if(ret == false) return false;
	}
	_Exchanges.insert({name,ecp});
	return true;
	}

删除交换机

根据交换机的名称进行一个删除。同时如果持久化存储了,也要删除数据库中的数据。

cpp 复制代码
void deleteExchange(const std::string &name){
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _Exchanges.find(name);
 if(it == _Exchanges.end()){
         return;
     }

     //删除数据库中数据
     if(it->second->durable == true){
         _mapper.remove(name);
     }
     _Exchanges.erase(name);
 }

获取指定交换机

根据交换机姓名获取指定交换机。

cpp 复制代码
//获取指定交换机
        Exchange::ptr selectExchange(const std::string &name){
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _Exchanges.find(name);
            if(it == _Exchanges.end()){
                //交换机不存在
                return Exchange::ptr();
            }

            return it->second;
        }

队列数据管理

队列和交换机管理的思想几乎一致,只不过有些字段不一样

管理的字段

队列名称:队列的唯一标识

持久化标志。

其他字段是扩展字段,暂时不关心。

cpp 复制代码
 struct MsgQueue
    {
        using ptr = std::shared_ptr<MsgQueue>;
        std::string name;                                  // 队列名称
        bool durable;                                      // 持久化标志位
        bool exclusive;                                    // 是否独占 (还未实现此功能)
        bool auto_delete;                                  // 自动删除 (未实现)
        google::protobuf::Map<std::string, std::string> args; // 其他参数
}

持久化管理类

和交换机一样,这里看一看表结构

cpp 复制代码
void createTable()
   {
        std::stringstream sql;
        sql << "create table if not exists queue_table(";
        sql << "name varchar(32) primary key, ";
        sql << "durable int, ";
        sql << "exclusive int, ";
        sql << "auto_delete int, ";
        sql << "args varchar(128));";
        assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
    }

内存管理类

都是一个持久化句柄,一个哈希表存储已经存在的队列信息。

cpp 复制代码
class MsgQueueManager
    {
    private:
        std::mutex _mutex;
        MsgQueueMapper _mapper;
        std::unordered_map<std::string, MsgQueue::ptr> _msg_queues;
    public:
        using ptr = std::shared_ptr<MsgQueueManager>;
        MsgQueueManager(const std::string &dbfile) : _mapper(dbfile)
        {
            //从数据库中读取,恢复队列数据
            _msg_queues = _mapper.recovery();
        }
}

申明/删除/获取指定队列

这里不过多介绍,都是一样的操作

获取所有队列

但是队列这边提供了一个额外的操作,获取所有队列信息。

这里直接构造一个哈希表返回。

我们的队列消息和消费者都是以队列为单元进行管理的。所以我们需要获取到已经存在队列,用来初始化队列消息和消费者管理。

cpp 复制代码
 //返回所有队列
std::unordered_map<std::string, MsgQueue::ptr> AllQueue()
   {
       std::unique_lock<std::mutex> lock(_mutex);
       return _msg_queues; //这里构造了一个
   }

绑定关系管理

管理的字段

交换机名称和队列名称,还有一个binding_key。

cpp 复制代码
 struct Binding
{
	using ptr = std::shared_ptr<Binding>;
	std::string exchange_name; // 交换机名称
	std::string msgqueue_name; // 队列名称
	std::string binding_key;
}

持久化管理类

绑定关系也是需要持久化管理的。当交换机和队列的持久化标志位都为true时,我们才将绑定关系持久化管理。这在虚拟机管理模块进行判断。

cpp 复制代码
void createTable()
 {
      std::stringstream sql;
      sql << "create table if not exists binding_table(";
      sql << "exchange_name varchar(32), ";
      sql << "msgqueue_name varchar(32), ";
      sql << "binding_key varchar(128));";
      assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
  }

内存管理类

内存管理类这块也是一个持久化管理句柄。

但是我们这里是交换机和队列的一个信息管理。

因为一个交换机可以绑定多个队列,而队列和绑定关系是一一对应的。

所以我们定义了两个类型,一个是队列和绑定的映射。一个是交换机和队列绑定的映射。我们实际管理的就是这个交换机和队列绑定的对象。

cpp 复制代码
using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;
cpp 复制代码
 class BindingManager
    {
    private:
        std::mutex _mutex;
        BindingMapper _mapper;
        BindingMap _bindings;
        
 using ptr = std::shared_ptr<BindingManager>;
        BindingManager(const std::string &dbfile) : _mapper(dbfile)
        {
            _bindings = _mapper.recovery();
        }
}

绑定/解除绑定

需要提供交换机名和队列名称以及binding_key和是否持久化。

这里的是否持久化是虚拟机判断后传入的。

这里也是存在及ok,不存在就创建的思想。

cpp 复制代码
bool bind(const std::string &ename, const std::string &qname, const std::string &key, bool durable)
 {
     std::unique_lock<std::mutex> lock(_mutex);
     auto eit = _bindings.find(ename);
     if (eit != _bindings.end() && (eit->second.find(qname) != eit->second.end()))
     {   
         //绑定数据已存在
         return true;
     }

     Binding::ptr bp = std::make_shared<Binding>(ename, qname, key);
     // 当交换机和队列的持久化标志位都为true,绑定数据才进行持久化,这个durable由外界判断后传入
     if (durable == true)
     {
         bool ret = _mapper.insert(bp);
         if (ret == false)
         {
             return false;
         }
     }
     //存在及获取,不存在及创建
     auto &MsgQueueMap = _bindings[ename];
     MsgQueueMap.insert({qname, bp});
     return true;
 }

删除绑定关系,一个交换机可以绑定多个队列,这里删除的只是一个交换机和队列的绑定关系。

cpp 复制代码
void unbind(const std::string &ename, const std::string &qname)
 {
     std::unique_lock<std::mutex> lock(_mutex);
     auto eit = _bindings.find(ename);
     //没有交换机信息,直接退出
     if(eit == _bindings.end()) { return; }
     auto qit = eit->second.find(qname);
     //没有队列相关信息,直接退出
     if(qit == eit->second.end()){ return ;}

     //判断持久化太麻烦了,所以这里不管持久化标志是否存在,我们直接去数据库中删除
     _mapper.remove(ename,qname);
     _bindings[ename].erase(qname);
 }

删除指定交换机的所有绑定关系

当交换机被删除时,需要删除该交换机的所有绑定关系

cpp 复制代码
//删除指定交换机的所有绑定数据 ---当删除交换机时需要删除交换机相关的所有绑定信息
void removeExchangeBindngs(const std::string &ename){
	//同样的这里不判断持久化,直接操作
	_mapper.removeExchangeBindings(ename);
	auto eit = _bindings.find(ename);
	if(eit == _bindings.end()){
	   //不存在直接return
	   return;
	}

	_bindings.erase(eit);
}

删除队列的所有绑定关系

当队列被删除后,需要删除该队列所有的绑定关系。

删除的方法就是遍历所有的交换机,因为一个队列是可以被多个交换机绑定的。

cpp 复制代码
 void removeMsgQueueBindings(const std::string &qname){
    std::unique_lock<std::mutex> lock(_mutex);
    //同样的这里不判断持久化,直接操作
    _mapper.removeMsgQueueBindings(qname);
    //遍历所有的交换机,因为一个交换机可以绑定多个队列,这个要删除的队列可能绑定了多个交换机
    for(auto eit = _bindings.begin(); eit != _bindings.end(); eit++){
        //eit->second是一个MsgQueueMap,这里判断的是这个队列是否存在这个map中,存在就删除
        auto qit = eit->second.find(qname);
        if(qit != eit->second.end()){
            eit->second.erase(qit);
        }
    }
}

获取指定的队列的所有绑定关系

这个接口非常重要,当交换机收到信息后,我们需要获取该交换机的所有绑定的队列,用来判断需要将消息转发到哪个队列。

cpp 复制代码
//获取交换机绑定的队列描述信息,当交换机收到消息时,需要将消息转给绑定的队列
  MsgQueueBindingMap getExchangeBindings(const std::string &ename){
      std::unique_lock<std::mutex> lock(_mutex);
      auto eit = _bindings.find(ename);
          if (eit == _bindings.end()) {
          return MsgQueueBindingMap();
      }

      return eit->second; 
  }

队列消息管理

管理的字段

这个模块有一些复杂,消息是需要被传输的,因此我们定义在了proto中,我们先看一下消息类型.

消息分为消息属性和消息正文。

消息属性中有三个字段,分别是消息的ID,routing_key和持久化模式。

消息正文就是一个string字段。

还有一个消息是否有效标志位。

也就是消息属性,消息正文,消息是否有效这三个字段是需要持久化存储的。我们把它定义为Payload字段。

其他两个字段是方便服务器进行管理二添加的字段。

cpp 复制代码
//消息属性
message BasicProperties {
    string id = 1; //消息ID
    string routing_key = 2; //与binding_key做匹配
    DeliveryMode delivery_mode = 3; //持久化模式 1-⾮持久化; 2-持久化
};


//消息结构
message Message {
    message Payload {
        BasicProperties properties = 1; //消息属性
        string body = 2; //有效载荷数据
        string valid = 3; //消息是否有效位
    };

    Payload payload = 1; //真正持久化的只有这⼀个字段
    uint64 offset = 2; //这两个字段⽤于记录消息在持久化⽂件中的位置和⻓度
    uint64 length = 3; //以便于在加载时可以在指定位置读取指定⻓度的数据获取到消息
};

持久化管理类

同样的消息也是需要进行持久化的,但是消息的持久化我们不是放在数据库中,而是存储在文件中。

因为有些消息会很大,不适合放在数据库。其次我们只是为了备份,不涉及到查询。

另外的我们的消息是以队列为单元进行管理的。

这是消息进行持久化管理的类,他有三个成员,一个是队列名称,另外两个是持久化数据文件的文件名。文件名就是用队列名加上.mqd后缀。

例如qname.mqd,这里还有一个tmpfile,原因是我们垃圾回收不是在源文件直接操作,而是遍历源文件,提取出有效消息,存入临时文件,最后在更改临时文件名称覆盖源文件。

cpp 复制代码
 class MessageManpper
    {
    private:
        std::string _qname;    // 队列名称
        std::string _datafile; // 保存消息持久化数据文件
        std::string _tmpfile;  // 垃圾回收时的临时文件
    }

插入消息数据

当收到一个消息后,如果消息的持久化标志位true,就需要对消息进行持久化存储了。

调用虚拟机的消息发布接口,然后调用到队列消息总的内存管理类。然后调用具体的一个队列消息管理类中的插入接口,进行插入。如果消息的持久化标志位是true,就需要持久化。

cpp 复制代码
bool insert(MessagePtr &msp)
{
    insert(_datafile, msp);
}

需要传入一个Message对象,这个对象是在队列消息管理的插入接口构造的.

这里的大致流程,将消息结构中的Payload结构序列化,然后获取到文件的大小,也就是我们要要存入的偏移量,在偏移量的位置先写入4字节的消息大小,也就是Payload的大小,然后写入序列化的数据。

最后会把偏移量和消息大小设置到MessagePtr中,这个Ptr会存储到消息链表和持久化哈希表中,也就同步跟新到内存了。

这里的偏移量是跳过4字节的,也就是指向的Payload。

cpp 复制代码
bool insert(const std::string &file, MessagePtr &msp)
{
    // 将消息数据中的有效载体序列化
    std::string body = msp->payload().SerializeAsString();

    FileHelper helper(file);
    size_t fsize = helper.size();
    size_t msg_size = body.size();
    // 往文件写入4字节数据长度
    bool ret = helper.write((char *)&msg_size, fsize, sizeof(size_t));
    if (ret == false)
    {
        DLOG("往队列文件 %s 写入数据长度失败", file.c_str());
        return false;
    }
    // DLOG("往队列文件 %s 写入数据长度:%d",file.c_str(),msg_size);

    // 往文件写入消息有效载荷
    ret = helper.write(body.c_str(), fsize + sizeof(size_t), msg_size);
    if (ret == false)
    {
        DLOG("往队列文件 %s 写入数据主体失败", file.c_str());
        return false;
    }
    // DLOG("往队列文件 %s 写入数据:%d",file.c_str(),body.c_str());

    // 更改消息数据的偏移量和长度
    msp->set_offset(fsize + sizeof(size_t));
    msp->set_length(msg_size);
    return true;
}

删除消息

当消息被确认后,就需要从文件中删除消息。而删除不是从文件中真的删除,而是将消息的有效位置0后,覆盖掉原文件中的消息。

这里需要传入一个MessagePtr,这个ptr就是收到了消费客户端的确认应答后,根据消息id在待确认哈希中找到对应的MessaePtr对象.

如果这个对象的持久化标志位1,就需要删除文件中的数据。

删除的流程就是将MessagePtr中的有效位置0,然后根据偏移量和消息长度,进行一个覆盖写入。

cpp 复制代码
 bool remove(MessagePtr &msp)
{
    // 将消息数据的有效位设置位'0'
    msp->mutable_payload()->set_valid("0");
    // 对msg进行序列化
    std::string body = msp->payload().SerializeAsString();
    if (body.size() != msp->length())
    {
        DLOG("不能修改文件中的数据信息,因为新生成的数据与原数据长度不一致!");
        return false;
    }

    FileHelper helper(_datafile);
    // 将消息数据覆盖写入到文件位置
    bool ret = helper.write(body.c_str(), msp->offset(), body.size());
    if (ret == false)
    {
        DLOG("向队列数据文件 %s 写入数据失败", _datafile);
        return false;
    }
    return true;
}

垃圾回收

这个垃圾回收会返回一个MessagePtr的链表,他会从文件中循环读取消息。先算出文件的大小,从0偏移量开始读取,先读取4字节消息长度,然后根据长度读取消息payload,盘后反序列化出一个Payload结构,判断有效位是否为1,为1则插入到链表中。循环结束就可以得到有效消息的链表。

然后将有效消息写入到临时文件,在写入到临时文件中消息的偏移量发生了改变,在内存中也存储了消息对象,所以需要同步更新消息的偏移量,所以我们的返回值是一个MessagePtr的链表。队列消息管理在进行了垃圾回收后可以进行跟新偏移量。

cpp 复制代码
std::list<MessagePtr> gc()
{
    std::list<MessagePtr> result;

    bool ret = load(result);
    if (ret == false)
    {
        DLOG("加载有效数据失败!\n");
        return result;
    }

    // DLOG("加载有效数据结束,数据个数:%d",result.size());

    // 将有效数据写入临时文件
    FileHelper::createFile(_tmpfile);   //必须先创建出临时文件,在datafile中没有数据时,不会进这个循环。会导致文件源文件被删除,tmp文件也没了
    FileHelper tmp_file_helper(_tmpfile);
    size_t offset = 0;
    for (auto &msp : result)
    {
        ret = insert(_tmpfile, msp);
        if (ret == false)
        {
            DLOG("向临时文件写入消息数据失败!!");
            return result;
        }
    }
    // DLOG("像临时文件写入数据结束,临时文件大小:%d",tmp_file_helper.size());
    ret = FileHelper::removeFile(_datafile);
    if (ret == false)
    {
        DLOG("删除源文件失败!");
        return result;
    }

    // 4. 修改临时文件名,为源文件名称
    ret = FileHelper(_tmpfile).rename(_datafile);
    if (ret == false)
    {
        DLOG("修改临时文件名称失败!");
        return result;
    }

    // 5. 返回新的有效数据
    return result;
}

队列消息内存管理类

队列消息是以队列为单元进行管理的,所以这个类是存在一个队列就要有一个。

和交换机/队列/绑定关系不同,这里复杂一些.

管理的字段

需要有一个持久化句柄,消息需要持久化。

然后一个队列名,该类所代表的队列。

一个有效消息数量和总消息数量。用来进行垃圾回收

一个待推送消息链表,收到消息后插入这个链表中。

一个持久化哈希表,用来垃圾回收后更新内存中消息的偏移量

一个待确认哈希表,当消息推送消费者后,需要把消息从带推送链表中删除,然后插入到待确认哈希表中。

cpp 复制代码
class QueueMessage
{
private:
    MessageManpper _manpper;    //持久化操作的句柄
    std::string _qname;  // 队列名
    size_t _valid_count; // 有效消息数量
    size_t _total_count; // 消息总数量
    std::mutex _mutex;
    std::list<MessagePtr> _msgs;                               // 带推送消息
    std::unordered_map<std::string, MessagePtr> _durable_msgs; // 持久化消息
    std::unordered_map<std::string, MessagePtr> _waitack_msgs; // 待确认消息
}

插入消息

插入消息是在服务器收到消息发布请求后,通过虚拟机句柄调用的,我们这里需要构造一个MEssagePTr.用户的请求中可能没有填入消息属性。如果没有填入的话,我们就构造一个属性字段填入,其中消息ID自动生成,持久化标志看队列是否持久化,routing_ket设置为空字符串。

cpp 复制代码
bool insert(const BasicProperties *bp, std::string body, bool queue_is_durable)

然后我们根据持久化标志位,将消息持久化存储,同时更新消息数量,然后插入进带推送消息链表中。

删除消息

是在客户端确认后,删除待确认哈希表中的消息。

如果消息时持久化的,需要删除持久化信息,同同时删除消息数量。

另外进行一次垃圾回收,垃圾回收需要满足总体消息数量达到2000条,且有效消息数量的个数不到总消息的%50;

垃圾回收就是调用持久化管理的gc接口,它会返回一个list< MessagePtr>,通过这个list,跟新内存消息的偏移量(遍历list,find查找持久化哈希表中的Message,进行更新)。同时更新消息数量。

cpp 复制代码
  void gc()
{
   if (GCCheck() == false)
       return;
   // 获取有效消息
   std::list<MessagePtr> msgs = _manpper.gc();//这里构造了一个list<MessagePtr>,他和我们的_durable_msgs中的MessagePtr是不同的,所以需要单独更改_durable_msgs中MessagePtr的偏移量
 for (auto &msg : msgs)
 {
     auto it = _durable_msgs.find(msg->payload().properties().id());
     if (it == _durable_msgs.end())
     {
         // 持久化文件中的消息,在内存中不存在
         DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理");
         _msgs.push_back(it->second); // 做法:将该消息插入进待推送链表的末尾
         _durable_msgs.insert({msg->payload().properties().id(), msg});
         continue;
     }

     // 更新每一条消息的实际存储位置
     it->second->set_offset(msg->offset());
     it->second->set_length(msg->length());
 }

 // 3. 更新当前的有效消息数量 & 总的持久化消息数量
 _valid_count = _total_count = msgs.size();
}

获取队首消息

当队列收到一个消息,就需要进行推送,删除消息链表的头部消息,然后插入到待确认哈希表中。

cpp 复制代码
 // 获取队首消息
MessagePtr front()
{
 std::unique_lock<std::mutex> lock(_mutex);

 if(_msgs.size() == 0){
     return MessagePtr();
 }

 // 从待推送链表中取出一个消息
 MessagePtr msp = _msgs.front();
 _msgs.pop_front();

 // 并将该消息添加进待确认哈希表
 _waitack_msgs.insert({msp->payload().properties().id(), msp});

 return msp;
}

总的消息内存管理类

一个文件路径,我们的消息持久化文件的路径。

一个哈希表,队列名称和队列消息管理类的映射。

我们对外提供的就是这个对象。

cpp 复制代码
 class MessageManager
{
private:
    std::string _basedir; //存储队列信息文件的路径,我们的队列信息是存储在文件中的
    std::mutex _mutex;
    std::unordered_map<std::string, QueueMessage::ptr> _queue_msgs; //队列名对应队列信息
}

在他的构造中,我们需要根据已经存在的队列创建出队列消息管理类。然后进行一个持久化消息的恢复。

cpp 复制代码
// 初始化一个队列消息管理,在创建队列的时候调用
void InitQueueMessage(const std::string &qname)
{
   QueueMessage::ptr tmp;
   {
       std::unique_lock<std::mutex> lock(_mutex);
       auto it = _queue_msgs.find(qname);
       if (it != _queue_msgs.end())
       {
           //DLOG("消息管理句柄 %s 已经存在", qname.c_str());
           return;
       }

       // 构建一个队列消息内存管理类对象
       QueueMessage::ptr qmp = std::make_shared<QueueMessage>(_basedir, qname);
       tmp = qmp;
       _queue_msgs.insert(std::make_pair(qname, qmp));
   }

   // 恢复历史数据,这个操作是非常耗时的,我们没有放在加锁里。
   tmp->recovery();
}

这里不想交换机,队列和绑定关系的恢复那么轻量,这里的消息数量会很大,因此我们没有在队列消息的构造函数中进行,而是单独提供了一个接口用来进行恢复。

恢复就是进行一次垃圾回收,然后返回一个链表,遍历链表,插入到内存管理中。

删除一个队列消息管理

当一个队列删除时,他的队列消息管理也就没有意义了,需要删除他的管理,同时删除对应的持久化文件数据。

cpp 复制代码
// 销毁一个队列消息管理,在删除队列的时候调用
  void DestoryQueueMessage(const std::string &qname)
    {
        QueueMessage::ptr tmp;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _queue_msgs.find(qname);
            if (it == _queue_msgs.end())
            {
                DLOG("消息管理句柄 %s 不存在", qname.c_str());
                return;
            }

            tmp = it->second;
            _queue_msgs.erase(qname);
        }

        tmp->clear();
    }

新增消息

这里都是对指定队列消息管理进行的操作。接口我们已经实现了,就是从哈希表中找到 指定的对象就行。

cpp 复制代码
// 新增一条消息
        bool insert(const std::string &qname, BasicProperties *bp, std::string body, bool queue_is_durable)
{
  QueueMessage::ptr tmp;
  {
      std::unique_lock<std::mutex> lock(_mutex);
      auto it = _queue_msgs.find(qname);
      if (it == _queue_msgs.end())
      {
          DLOG("向队列%s新增消息失败:没有找到消息管理句柄!", qname.c_str());
          return false;
      }

      tmp = it->second;
  }

  return tmp->insert(bp, body, queue_is_durable);
}

获取队首消息

cpp 复制代码
 MessagePtr front(const std::string &qname)
{
  QueueMessage::ptr tmp;
  {
      std::unique_lock<std::mutex> lock(_mutex);
      auto it = _queue_msgs.find(qname);
      if (it == _queue_msgs.end())
      {
          DLOG("向队列%s获取队首消息失败:没有找到消息管理句柄!", qname.c_str());
          return MessagePtr();
      }

      tmp = it->second;
  }

  return tmp->front();
}

确认消息

cpp 复制代码
 void ack(const std::string &qname, const std::string &msg_id)
{
  QueueMessage::ptr tmp;
  {
      std::unique_lock<std::mutex> lock(_mutex);
      auto it = _queue_msgs.find(qname);
      if (it == _queue_msgs.end())
      {
          DLOG("向队列%s确认消息失败:没有找到消息管理句柄!", qname.c_str());
          return;
      }

      tmp = it->second;
  }

  tmp->remove(msg_id);
  return;
}
相关推荐
Allen Bright11 小时前
RabbitMQ中的普通Confirm模式:深入解析与最佳实践
分布式·rabbitmq
S-X-S11 小时前
集成RabbitMQ+MQ常用操作
rabbitmq
dzend11 小时前
Kafka、RocketMQ、RabbitMQ 对比
kafka·rabbitmq·rocketmq
小林想被监督学习21 小时前
RabbitMQ 的7种工作模式
分布式·rabbitmq
wy02_1 天前
Linux下载RabbitMQ,并解决Github拒绝访问443的问题
linux·rabbitmq·github
浩哲Zhe2 天前
RabbitMQ
java·分布式·rabbitmq
Allen Bright2 天前
RabbitMQ中的Topic模式
分布式·rabbitmq
Allen Bright2 天前
Spring Boot 整合 RabbitMQ:手动 ACK 与 QoS 配置详解
spring boot·rabbitmq·java-rabbitmq
一路狂飙的猪2 天前
RabbitMQ的工作模型
分布式·rabbitmq
来一杯龙舌兰3 天前
【RabbitMQ】RabbitMQ保证消息不丢失的N种策略的思想总结
分布式·rabbitmq·ruby·持久化·ack·消息确认