rabbitmq----数据管理模块

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

文章目录

  • 交换机数据管理
    • 管理的字段
    • 持久化管理类
    • 内存管理类
      • 申明交换机
      • 删除交换机
      • 获取指定交换机
  • 队列数据管理
    • 管理的字段
    • 持久化管理类
    • 内存管理类
      • 申明/删除/获取指定队列
      • 获取所有队列
  • 绑定关系管理
    • 管理的字段
    • 持久化管理类
    • 内存管理类
      • 绑定/解除绑定
      • 删除指定交换机的所有绑定关系
      • 删除队列的所有绑定关系
      • 获取指定的队列的所有绑定关系
  • 队列消息管理
    • 管理的字段
    • 持久化管理类
      • 插入消息数据
      • 删除消息
      • 垃圾回收
    • 队列消息内存管理类
      • 管理的字段
      • 插入消息
      • 删除消息
      • 获取队首消息
  • 总的消息内存管理类
      • 删除一个队列消息管理
      • 新增消息
      • 获取队首消息
      • 确认消息


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

交换机数据管理

管理的字段

需要管理的数据有下面这个5个
交换机名称:交换机的唯一标识
交换机类型:交换机有三种类型,直接交换/广播交换/主题交换。决定了消息的转发方式。
持久化标识:决定了交换机信息是否持久化存储。方便断电后恢复。
剩下的俩个字段不需要关心,是为了以后进行扩展的。

 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,可以在这个数据库种创建多个表。
我们的交换机/队列/绑定关系信息的数据都是存储在这个文件种的.

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

这就是创建的交换机表。

#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(); // 直接异常退出程序
}

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

//返回交换机表中所有数据,用于重启后恢复
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;	
}

内存管理类

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

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

//交换机数据内存管理类,这个类才是对外提供的
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,不存在就构建一个交换机对象,插入哈希表

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()){//存在直接returnreturn 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;}

删除交换机

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

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

获取指定交换机

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

//获取指定交换机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;}

队列数据管理

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

管理的字段

队列名称:队列的唯一标识
持久化标志。
其他字段是扩展字段,暂时不关心。

 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; // 其他参数
}

持久化管理类

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

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

内存管理类

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

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

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

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

获取所有队列

但是队列这边提供了一个额外的操作,获取所有队列信息。
这里直接构造一个哈希表返回。
我们的队列消息和消费者都是以队列为单元进行管理的。所以我们需要获取到已经存在队列,用来初始化队列消息和消费者管理。

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

绑定关系管理

管理的字段

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

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

持久化管理类

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

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

内存管理类

内存管理类这块也是一个持久化管理句柄。
但是我们这里是交换机和队列的一个信息管理。
因为一个交换机可以绑定多个队列,而队列和绑定关系是一一对应的。
所以我们定义了两个类型,一个是队列和绑定的映射。一个是交换机和队列绑定的映射。我们实际管理的就是这个交换机和队列绑定的对象。

using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;
 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,不存在就创建的思想。

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;}

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

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

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

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

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

删除队列的所有绑定关系

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

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

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

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

//获取交换机绑定的队列描述信息,当交换机收到消息时,需要将消息转给绑定的队列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字段。
其他两个字段是方便服务器进行管理二添加的字段。

//消息属性
message BasicProperties {string id = 1; //消息IDstring 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,原因是我们垃圾回收不是在源文件直接操作,而是遍历源文件,提取出有效消息,存入临时文件,最后在更改临时文件名称覆盖源文件。

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

插入消息数据

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

bool insert(MessagePtr &msp)
{insert(_datafile, msp);
}

需要传入一个Message对象,这个对象是在队列消息管理的插入接口构造的.
这里的大致流程,将消息结构中的Payload结构序列化,然后获取到文件的大小,也就是我们要要存入的偏移量,在偏移量的位置先写入4字节的消息大小,也就是Payload的大小,然后写入序列化的数据。
最后会把偏移量和消息大小设置到MessagePtr中,这个Ptr会存储到消息链表和持久化哈希表中,也就同步跟新到内存了。
这里的偏移量是跳过4字节的,也就是指向的Payload。

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,然后根据偏移量和消息长度,进行一个覆盖写入。

 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的链表。队列消息管理在进行了垃圾回收后可以进行跟新偏移量。

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;
}

队列消息内存管理类

队列消息是以队列为单元进行管理的,所以这个类是存在一个队列就要有一个。
和交换机/队列/绑定关系不同,这里复杂一些.

管理的字段

需要有一个持久化句柄,消息需要持久化。
然后一个队列名,该类所代表的队列。
一个有效消息数量和总消息数量。用来进行垃圾回收
一个待推送消息链表,收到消息后插入这个链表中。
一个持久化哈希表,用来垃圾回收后更新内存中消息的偏移量
一个待确认哈希表,当消息推送消费者后,需要把消息从带推送链表中删除,然后插入到待确认哈希表中。

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设置为空字符串。

bool insert(const BasicProperties *bp, std::string body, bool queue_is_durable)

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

删除消息

是在客户端确认后,删除待确认哈希表中的消息。
如果消息时持久化的,需要删除持久化信息,同同时删除消息数量。
另外进行一次垃圾回收,垃圾回收需要满足总体消息数量达到2000条,且有效消息数量的个数不到总消息的%50;
垃圾回收就是调用持久化管理的gc接口,它会返回一个list< MessagePtr>,通过这个list,跟新内存消息的偏移量(遍历list,find查找持久化哈希表中的Message,进行更新)。同时更新消息数量。

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

获取队首消息

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

 // 获取队首消息
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;
}

总的消息内存管理类

一个文件路径,我们的消息持久化文件的路径。
一个哈希表,队列名称和队列消息管理类的映射。
我们对外提供的就是这个对象。

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

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

// 初始化一个队列消息管理,在创建队列的时候调用
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();
}

这里不想交换机,队列和绑定关系的恢复那么轻量,这里的消息数量会很大,因此我们没有在队列消息的构造函数中进行,而是单独提供了一个接口用来进行恢复。
恢复就是进行一次垃圾回收,然后返回一个链表,遍历链表,插入到内存管理中。

删除一个队列消息管理

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

// 销毁一个队列消息管理,在删除队列的时候调用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();}

新增消息

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

// 新增一条消息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);
}

获取队首消息

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

确认消息

 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/880977.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows配置C++编译环境和VScode C++配置(保姆级教程)

1.安装MinGW-w64 MinGW-w64是一个开源的编译器套件&#xff0c;适用于Windows平台&#xff0c;支持32位和64位应用程序的开发。它包含了GCC编译器、GDB调试器以及其他必要的工具&#xff0c;是C开发者在Windows环境下进行开发的重要工具。 我找到了一个下载比较快的链接&#…

初识Linux · 自主Shell编写

目录 前言&#xff1a; 1 命令行解释器部分 2 获取用户命令行参数 3 命令行参数进行分割 4 执行命令 5 判断命令是否为内建命令 前言&#xff1a; 本文介绍是自主Shell编写&#xff0c;对于shell&#xff0c;即外壳解释程序&#xff0c;我们目前接触到的命令行解释器&am…

技术成神之路:设计模式(十八)适配器模式

介绍 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许接口不兼容的类可以协同工作&#xff0c;通过将一个类的接口转换成客户端所期望的另一个接口&#xff0c;使得原本由于接口不兼容而不能一起工作的类可以一起工作。 1.定义 适配…

基础算法--枚举

枚举算法是一种简单而有效的算法&#xff0c;它通过枚举所有可能的情况来解决问题。它通常用于解决问题规模比较小的问题&#xff0c;因为它的时间复杂度很高&#xff0c;随着问题的规模增加&#xff0c;算法的效率会急剧下降。 枚举算法的基本思路是通过循环遍历所有可能的情…

CSS实现服务卡片

CSS实现服务卡片 效果展示 CSS 知识点 回顾整体CSS知识点灵活运用CSS知识点 页面整体布局 <div class"container"><div class"card"><div class"box"><div class"icon"><ion-icon name"color-pal…

记录一次病毒启动脚本

在第一次下载软件时&#xff0c;目录中配了一个使用说明&#xff0c;说是需要通过start.bat 这个文件来启动程序&#xff0c;而这个 start.bat 就是始作俑者&#xff1a; 病毒作者比较狡猾&#xff0c;其中start.bat 用记事本打开是乱码&#xff0c;但是可以通过将这个批处理…

OpenMV与STM32通信全面指南

目录 引言 一、OpenMV和STM32简介 1.1 OpenMV简介 1.2 STM32简介 二、通信协议概述 三、硬件连接 3.1 硬件准备 3.2 引脚连接 四、软件环境搭建 4.1 OpenMV IDE安装 4.2 STM32开发环境 五、UART通信实现 5.1 OpenMV端编程 5.2 STM32端编程 六、SPI通信实现 6.1 …

查缺补漏----I/O中断处理过程

中断优先级包括响应优先级和处理优先级&#xff0c;响应优先级由硬件线路或查询程序的查询顺序决定&#xff0c;不可动态改变。处理优先级可利用中断屏蔽技术动态调整&#xff0c;以实现多重中断。下面来看他们如何运用在中断处理过程中&#xff1a; 中断控制器位于CPU和外设之…

动态规划最长上升子序列问题讲解和【题解】——最长上升子序列

动态规划最长上升子序列讲解和题解——最长上升子序列 最长上升子序列问题讲解1.概念解析2.举例了解3.示例程序 最长上升子序列题目描述输入格式输出格式输入输出样例输入 #1输出 #1 提示思路解析 最长上升子序列问题讲解 1.概念解析 最长上升子序列 &#xff08; L o n g e s…

微服务sentinel解析部署使用全流程

sentinel源码地址&#xff1a; 介绍 alibaba/Sentinel Wiki GitHub sentinel官方文档&#xff1a; https://sentinelguard.io/zh-cn/docs/introduction.html Sprong Cloud alibaba Sentinel文档【小例子】 : Sentinel alibaba/spring-cloud-alibaba Wiki GitHub 目录 1、…

C# + SQLiteExpert 进行(cipher)加密数据库开发+Costura.Fody 清爽发布

一&#xff1a;让 SQLiteExpert 支持&#xff08;cipher&#xff09;加密数据库 SQLiteExpert 作为SQlite 的管理工具&#xff0c;默认不支持加密数据库的&#xff0c;使其成为支持&#xff08;cipher&#xff09;加密数据库的管理工具&#xff0c;需要添加e_sqlcipher.dll &…

Android-Handle消息传递和线程通信

本文为作者学习笔记&#xff0c;如有误&#xff0c;请各位大佬指点 目录 一、同步异步 二、Java多线程通信 三、Handler是什么 四、Handler相关的类 五、Handler常用方法 1. 发送消息 2. 接收处理消息 3. 切换线程 六、使用Handler 使用Handler更新UI 使用Handler延…

蓝桥杯【物联网】零基础到国奖之路:十八. 扩展模块之光敏和AS312

蓝桥杯【物联网】零基础到国奖之路:十八.扩展模块之光敏和AS312 第一节 硬件解读第二节 CubeMX配置第二节 代码 第一节 硬件解读 光敏和AS312如下图&#xff1a; 光敏电阻接到了扩展模块的5号引脚&#xff0c;5号引脚接了2个电阻&#xff0c;R8和光敏电阻。我们通过ADC读取这…

Python 从入门到实战33(使用MySQL)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库编程接口操作的相关知识。今天我们将学习…

ASP.NET Zero 多租户介绍

ASP.NET Zero 是一个基于 ASP.NET Core 的应用程序框架&#xff0c;它提供了多租户支持&#xff0c;以下是关于 ASP.NET Zero 多租户的介绍&#xff1a; 一、多租户概念 多租户是一种软件架构模式&#xff0c;允许多个客户&#xff08;租户&#xff09;共享同一套软件应用程序…

探索TOGAF理论的实践应用:企业数字化转型的深度指南

数字化转型的迫切性与路径选择 随着全球化进程和技术革命的加速&#xff0c;企业正面临前所未有的挑战和机遇。数字化转型已成为企业保持竞争力、创新业务模式、优化客户体验的核心手段。然而&#xff0c;企业在实施数字化转型时&#xff0c;往往面临路径不清、技术与业务脱节…

《Linux从小白到高手》理论篇(七):Linux的时间管理运行级别启动过程原理详解

List item 本篇将介绍Linux的时间管理&运行级别相关知识&#xff0c;并将深入介绍Linux的启动过程及原理。 Linux的时间管理 Linux 时钟分为系统时钟&#xff08;System Clock&#xff09;和硬件&#xff08;Real Time Clock&#xff0c;简称 RTC&#xff09;时钟。系统时…

Linux驱动开发(速记版)--设备树插件

第六十八章 设备树插件介绍 Linux 4.4之后引入了动态设备树&#xff0c;其中的设备树插件&#xff08;Device Tree Overlay&#xff09;是一种扩展机制&#xff0c;允许在运行时动态添加、修改或删除设备节点和属性。 设备树插件机制通过DTS&#xff08;设备树源文件&#xff0…

protobuf 讲解

一、序列化概念回顾 二、什么是PB 将结构化数据进行序列化的一种方式 三、PB的特点 语言无关、平台无关&#xff1a;即PB支持Java&#xff0c;C、Python等多种语言。支持多个平台 高效&#xff1a;即比XML更小&#xff0c;更快&#xff0c;更为简单。 扩展性、兼容性好&am…

WPF之UI进阶--控件样式与样式模板及词典

WPF的优势之一就是能够更加容易快捷的对窗体和控件的外面进行改造&#xff0c;换句话说&#xff0c;那就是UI设计个性化更加容易。主要是借助了样式、模板及词典来实现的。那么本篇博文就一一对他们进行介绍。 文章目录 一、样式1: 定义样式2: 使用Setter设置属性关于Property和…