项目第四弹:交换机、队列、绑定信息管理模块分析与代码实现

项目第四弹:交换机、队列、绑定信息管理模块分析与代码实现

  • 一、模块设计分析
    • 1.模块划分
    • 2.功能需求
  • 二、交换机模块的实现
    • 1.交换机结构体的实现
    • 2.交换机持久化管理模块的实现
    • 3.交换机对外管理模块实现
      • 声明、删除交换机时的查找不能复用exists函数
      • 为何持久化管理模块无需事务
    • 4.测试代码
  • 三、队列模块的实现
    • 1.功能需求
    • 2.队列结构体的实现
    • 3.队列持久化管理模块的实现
    • 4.队列对外管理模块的实现
    • 5.测试代码
  • 四、绑定信息管理模块的实现
    • 1.功能需求
    • 2.Binding结构体实现
    • 3.版本一
      • 1.绑定信息持久化管理类
      • 2.绑定信息管理类
    • 4.版本二
      • 1.绑定信息持久化管理类
      • 2.绑定信息管理类
    • 5.测试

一、模块设计分析

我们以交换机为例来分析,队列跟绑定信息和它的设计思路是一样的

1.模块划分

在这里插入图片描述
这个对外管理其实就是对交换机、队列、绑定信息的:
增、删、查(没有改,不提供改操作,你可以删了重新新增,但是不能改)
因为多线程环境下改交换机,队列,绑定信息破坏系统的原子性和一致性,导致数据不一致的状态

而且RabbitMQ的设计哲学就是保持简单和可预测。通过提供明确和有限的操作接口,RabbitMQ能够确保其行为在不同场景下都是可预测的,从而降低了使用和维护的难度

可以看作是一种代码健壮性,可维护性和YAGNI原则(You Aren’t Gonna Need It 你不会需要它)的贯彻

2.功能需求

  1. Exchange
    - 【1】交换机名称
    - 【2】交换机类型
    - 【3】持久化标志
    - 【4】 自动删除标志
    - 【5】其他参数【<key,value>的一个google::protobuf::Map】

  2. ExchangeMapper

    1. 对外接口
      • 【1】创建/删除交换机表
      • 【2】新增/删除数据
      • 【3】查询所有交换机数据(用于恢复持久化的交换机数据)
    2. 管理对象
      • 【1】SqliteHelper的句柄
  3. ExchangeManager

    1. 对外接口
      • 【1】声明/删除交换机
      • 【2】获取指定交换机
      • 【3】销毁所有交换机
    2. 管理对象
      • 【1】保证线程安全的互斥锁
      • 【2】<交换机名称,交换机对象智能指针>的一个哈希表
      • 【3】ExchangeMapper句柄

二、交换机模块的实现

1.交换机结构体的实现

之前说过,交换机类型需要持久化,因为需要通过网络IO,因此这个其他参数的Map也需要持久化
所以需要用protobuf当中的Map

namespace ns_Exchange
{const std::string inner_sep = "=";const std::string outer_sep = "&";
}struct Exchange
{using ptr = std::shared_ptr<Exchange>;Exchange() = default;Exchange(const std::string &ename, ExchangeType etype, bool edurable, bool eauto_delete,const google::protobuf::Map<std::string, std::string> &eargs): name(ename), type(etype), durable(edurable), auto_delete(eauto_delete), args(eargs) {}std::string name;ExchangeType type;bool durable;bool auto_delete;google::protobuf::Map<std::string, std::string> args;// 因为交换机要持久化,而sqlite里面没有kv结构类型,所以需要进行序列化和反序列化,存字符串// 定义格式 key=val&key=val// 因此需要我们提供args的序列化和反序列化操作std::string getArgs(){std::string ret;for (auto &kv : args){ret += kv.first + ns_Exchange::inner_sep + kv.second;ret += ns_Exchange::outer_sep;}int sz = ns_Exchange::outer_sep.size();while (!ret.empty() && sz--){ret.pop_back();}return ret;}void setArgs(const std::string &str){// 使用StringHelper的字符串切割函数将key=val&key=val切分为key=val key=val// 然后按照=进行分割即可std::vector<std::string> vec;StringHelper::split(str, ns_Exchange::outer_sep, &vec);for (auto &elem : vec){size_t pos = elem.find(ns_Exchange::inner_sep);args.insert({elem.substr(0, pos), elem.substr(pos + 1)});}}
};

就是一个结构体定义+俩构造函数+Map的序列和反序列化(自定义小协议解决粘包)

2.交换机持久化管理模块的实现

using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;const int arg_len = 512;class ExchangeMapper
{
public:// 构造当中传入数据库名称ExchangeMapper(const std::string &dbfile): _helper(dbfile){if (!_helper.open()){default_fatal("交换机持久化管理模块初始化失败, 因为数据库打开失败");abort();}if (!createTable()){default_fatal("交换机持久化管理模块初始化失败, 因为表的创建失败");abort();}}// 因为SqliteHelper在析构的时候会自动调用其内部的close,所以不用管~ExchangeMapper() = default;// 创建/删除表bool createTable(){// 为了代码健壮性std::ostringstream create_sql;create_sql << "create table if not exists exchange_table(\name varchar(32) primary key,\type int,\durable int,\auto_delete int,\args varchar(";create_sql << arg_len << "));";if (!_helper.exec(create_sql.str(), nullptr, nullptr)){default_fatal("创建交换机数据表失败");return false;}return true;}bool dropTable(){static std::string drop_sql = "drop table if exists exchange_table;";if (!_helper.exec(drop_sql, nullptr, nullptr)){default_fatal("删除交换机数据表失败");return false;}return true;}// 插入/删除数据bool insert(const Exchange::ptr &ep){if (ep.get() == nullptr){default_fatal("无法将该数据插入交换机表当中,因为该数据为空指针");return false;}std::ostringstream insert_sql;insert_sql << "insert into exchange_table values(";insert_sql << "'" << ep->name << "',";insert_sql << ep->type << ",";insert_sql << ep->durable << ",";insert_sql << ep->auto_delete << ",";insert_sql << "'" << ep->getArgs() << "');";if (!_helper.exec(insert_sql.str(), nullptr, nullptr)){default_error("插入交换机数据失败,exchange_name: %s",ep->name.c_str());return false;}return true;}bool erase(const std::string &ename){std::ostringstream erase_sql;erase_sql << "delete from exchange_table where name=" << "'" << ename << "';";if (!_helper.exec(erase_sql.str(), nullptr, nullptr)){default_error("删除交换机数据失败,exchange_name: %s",ename.c_str());return false;}return true;}// 查询所有交换机数据ExchangeMap recovery(){static std::string select_sql = "select * from exchange_table;";ExchangeMap emap;if (!_helper.exec(select_sql, &ExchangeMapper::SelectCallback, &emap)){default_error("查询交换机所有数据失败");return ExchangeMap();}return emap;}private:static int SelectCallback(void *arg, int column, char **row, char **field){ExchangeMap *eptr = static_cast<ExchangeMap *>(arg);Exchange::ptr ep = std::make_shared<Exchange>();ep->name = row[0];ep->type = static_cast<ExchangeType>(std::stoi(row[1])); // 字符串 -> 整形 -> 枚举ep->durable = static_cast<bool>(std::stoi(row[2]));ep->auto_delete = static_cast<bool>(std::stoi(row[3]));ep->setArgs(row[4]);eptr->insert(std::make_pair(ep->name, ep));return 0;}SqliteHelper _helper;
};

其实就是写SQL语句,执行,打印错误日志。
那个selectCallback跟我们之前写的本质是同一个思想,其实就是传入一个大型容器
然后在内部创建填好一个小容器,然后把小容器放到大容器当中即可
甚至上面那个还更简单

int SelectCallback(void *arg, int column, char **rows, char **fields)
{vector<vector<string>> *total_elem = static_cast<vector<vector<string>> *>(arg);vector<string> one_elem;if (total_elem->empty()){for (int i = 0; i < column; i++){one_elem.push_back(fields[i]);}total_elem->push_back(move(one_elem));}for (int i = 0; i < column; i++){one_elem.push_back(rows[i]);}total_elem->push_back(move(one_elem));return 0;
}

在这里插入图片描述

3.交换机对外管理模块实现

对外管理其实就是增删查,也没啥难写的

class ExchangeManager
{
public:using ptr = std::shared_ptr<ExchangeManager>;ExchangeManager(const std::string &dbfile): _mapper(dbfile){// 恢复所有交换机_emap = _mapper.recovery();}bool declareExchange(const std::string &ename, ExchangeType etype, bool edurable, bool eauto_delete,const google::protobuf::Map<std::string, std::string> &eargs){// 0. 加锁std::unique_lock<std::mutex> ulock(_mutex);if (_emap.count(ename)) // 有该交换机return true;// 1. 构建Exchange::ptr对象(这里也需要锁,因为判断+创建才是个完整的声明交换机的操作,要保证他的原子性)Exchange::ptr ep = std::make_shared<Exchange>(ename, etype, edurable, eauto_delete, eargs);// 2. 看是否需要持久化if (edurable){if (!_mapper.insert(ep)){default_error("声明交换机持久化数据失败, 交换机名称:%s",ename.c_str());return false;}}// 3. 放到_emap当中_emap.insert(std::make_pair(ename, ep));return true;}bool eraseExchange(const std::string &ename){// 0. 加锁std::unique_lock<std::mutex> ulock(_mutex);// 1. 在emap中查找该交换机auto iter = _emap.find(ename);if (iter == _emap.end())return true;// 2. 看该交换机是否持久化了Exchange::ptr ep = iter->second;if (ep->durable){// 3. 持久化删除if (!_mapper.erase(ename)){default_error("删除交换机持久化数据失败, 交换机名称: %s",ename.c_str());return false;}}// 4. 内存删除_emap.erase(iter);return true;}ExchangeMap getAllExchanges(){std::unique_lock<std::mutex> ulock(_mutex);return _emap;}Exchange::ptr getExchange(const std::string &ename){std::unique_lock<std::mutex> ulock(_mutex);auto iter = _emap.find(ename);if (iter == _emap.end()){return Exchange::ptr();}return iter->second;}// ExchangeManager的析构函数不许调用clear// 因为持久化数据的清理只能由用户决定void clear(){std::unique_lock<std::mutex> ulock(_mutex);_mapper.dropTable();_emap.clear();}bool exists(const std::string &ename){std::unique_lock<std::mutex> ulock(_mutex);return _emap.count(ename) > 0;}private:ExchangeMapper _mapper;std::mutex _mutex;ExchangeMap _emap;
};

声明、删除交换机时的查找不能复用exists函数

在这里插入图片描述
其实上面就是增删查而已
大家都写过这么多代码了,这种代码没啥难的,仔细看看就搞定了
他们仨的核心其实都是一样的,所以我们放在一起来介绍
在这里插入图片描述

为何持久化管理模块无需事务

因为它们都只是涉及到一条简单的SQL语句而已,并不是多条组成
也不是复杂的多表连接等等,操作本身就是原子的,要么成功,要么失败
所以无需事务来保证ACID 原子性,一致性,隔离性,持久性
天然就有这四种特性

4.测试代码

用GTest的全局测试框架来搞

#include <gtest/gtest.h>
#include "../mqserver/exchange.hpp"
// 因为全局测试套件更方便,所以我们用它
using namespace ns_mq;ExchangeManager::ptr emp;class ExchangeTest : public testing::Environment
{
public:virtual void SetUp(){emp = std::make_shared<ExchangeManager>("test.db");}virtual void TearDown(){emp->clear();}
};TEST(exchange_test, recovery_test)
{ASSERT_EQ(emp->exists("exchange1"), true);ASSERT_EQ(emp->exists("exchange2"), true);ASSERT_EQ(emp->exists("exchange3"), false);ASSERT_EQ(emp->exists("exchange4"), false);
}TEST(exchange_test, insert_test)
{google::protobuf::Map<std::string, std::string> emap;emap.insert({"k1", "v1"});emap.insert({"k2", "v2"});emp->declareExchange("exchange1", FANOUT, true, false, emap);emp->declareExchange("exchange2", DIRECT, true, false, emap);emp->declareExchange("exchange3", DIRECT, true, false, emap);emp->declareExchange("exchange4", DIRECT, false, false, emap);ASSERT_EQ(emp->exists("exchange1"), true);ASSERT_EQ(emp->exists("exchange2"), true);ASSERT_EQ(emp->exists("exchange3"), true);ASSERT_EQ(emp->exists("exchange4"), true);
}TEST(exchange_test, select_test)
{Exchange::ptr eptr = emp->getExchange("exchange1");ASSERT_EQ(eptr->type, FANOUT);ASSERT_EQ(eptr->durable, true);ASSERT_EQ(eptr->auto_delete, false);
}TEST(exchange_test, erase_test)
{bool ret = emp->eraseExchange("exchange3");ASSERT_EQ(ret, true);ASSERT_EQ(emp->exists("exchange1"), true);ASSERT_EQ(emp->exists("exchange2"), true);ASSERT_EQ(emp->exists("exchange3"), false);ASSERT_EQ(emp->exists("exchange4"), true);
}int main(int argc, char *argv[])
{testing::AddGlobalTestEnvironment(new ExchangeTest);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

这代码也不难,毕竟我们之前都用过GTest了,大家按自己的方式来测试就行了

三、队列模块的实现

1.功能需求

  1. MsgQueue
    - 【1】 队列名称
    - 【2】 持久化标志
    - 【3】 是否独占标志(true:只有创建该队列的消费者才能订阅该队列)
    - 【4】 自动删除标志
    - 【5】 其他参数【<key,value>的一个google::protobuf::Map】

  2. MsgQueueMapper
    1. 对外接口
    - 【1】 创建/删除表
    - 【2】 新增/删除数据
    - 【3】 查询所有队列数据
    2. 管理对象
    - 【1】 SqliteHelper句柄

  3. MsgQueueManager
    1. 对外接口
    - 【1】 声明/删除队列
    - 【2】 查询指定队列
    - 【3】 销毁所有队列
    2. 管理对象
    - 【1】 互斥锁
    - 【2】 <队列名称,队列数据智能指针>的一个哈希表
    - 【3】MsgQueueMapper句柄

2.队列结构体的实现

namespace ns_MsgQueue
{const std::string inner_sep = "=";const std::string outer_sep = "&";
}
struct MsgQueue
{using ptr = std::shared_ptr<MsgQueue>;MsgQueue() = default;MsgQueue(const std::string &qname, bool qdurable, bool qexclusive, bool qauto_delete,const google::protobuf::Map<std::string, std::string> &qargs): name(qname), durable(qdurable), exclusive(qexclusive), auto_delete(qauto_delete), args(qargs) {}std::string name;bool durable;bool exclusive;bool auto_delete;google::protobuf::Map<std::string, std::string> args;std::string getArgs(){std::string ret;for (auto &kv : args){ret += kv.first + ns_MsgQueue::inner_sep + kv.second;ret += ns_MsgQueue::outer_sep;}int sz = ns_MsgQueue::outer_sep.size();while (!ret.empty() && sz--){ret.pop_back();}return ret;}void setArgs(const std::string &str){std::vector<std::string> vec;StringHelper::split(str, ns_MsgQueue::outer_sep, &vec);for (auto &elem : vec){size_t pos = elem.find(ns_MsgQueue::inner_sep);args.insert({elem.substr(0, pos - 1), elem.substr(pos + 1)});}}
};

跟交换机雷同,也就是成员有一点不一样而已

3.队列持久化管理模块的实现

using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;const int earg_len = 512;class ExchangeMapper
{
public:// 构造当中传入数据库名称ExchangeMapper(const std::string &dbfile): _helper(dbfile){if (!_helper.open()){default_fatal("交换机持久化管理模块初始化失败, 因为数据库打开失败");abort();}if (!createTable()){default_fatal("交换机持久化管理模块初始化失败, 因为表的创建失败");abort();}}// 因为SqliteHelper在析构的时候会自动调用其内部的close,所以不用管~ExchangeMapper() = default;// 创建/删除表bool createTable(){// 为了代码健壮性std::ostringstream create_sql;create_sql << "create table if not exists exchange_table(\name varchar(32) primary key,\type int,\durable int,\auto_delete int,\args varchar(";create_sql << earg_len << "));";if (!_helper.exec(create_sql.str(), nullptr, nullptr)){default_fatal("创建交换机数据表失败");return false;}return true;}bool dropTable(){static std::string drop_sql = "drop table if exists exchange_table;";if (!_helper.exec(drop_sql, nullptr, nullptr)){default_fatal("删除交换机数据表失败");return false;}return true;}// 插入/删除数据bool insert(const Exchange::ptr &ep){if (ep.get() == nullptr){default_fatal("无法将该数据插入交换机表当中,因为该数据为空指针");return false;}std::ostringstream insert_sql;insert_sql << "insert into exchange_table values(";insert_sql << "'" << ep->name << "',";insert_sql << ep->type << ",";insert_sql << ep->durable << ",";insert_sql << ep->auto_delete << ",";insert_sql << "'" << ep->getArgs() << "');";if (!_helper.exec(insert_sql.str(), nullptr, nullptr)){default_fatal("插入交换机数据失败,exchange_name: %s",ep->name.c_str());return false;}return true;}bool erase(const std::string &ename){std::ostringstream erase_sql;erase_sql << "delete from exchange_table where name=" << "'" << ename << "';";if (!_helper.exec(erase_sql.str(), nullptr, nullptr)){default_error("删除交换机数据失败,exchange_name: %s",ename.c_str());return false;}return true;}// 查询所有交换机数据ExchangeMap recovery(){static std::string select_sql = "select * from exchange_table;";ExchangeMap emap;if (!_helper.exec(select_sql, &ExchangeMapper::SelectCallback, &emap)){default_error("查询交换机所有数据失败");return ExchangeMap();}return emap;}private:static int SelectCallback(void *arg, int column, char **row, char **field){ExchangeMap *eptr = static_cast<ExchangeMap *>(arg);Exchange::ptr ep = std::make_shared<Exchange>();ep->name = row[0];ep->type = static_cast<ExchangeType>(std::stoi(row[1])); // 字符串 -> 整形 -> 枚举ep->durable = static_cast<bool>(std::stoi(row[2]));ep->auto_delete = static_cast<bool>(std::stoi(row[3]));ep->setArgs(row[4]);eptr->insert(std::make_pair(ep->name, ep));return 0;}SqliteHelper _helper;
};

跟交换机雷同

4.队列对外管理模块的实现

class MsgQueueManager
{
public:using ptr = std::shared_ptr<MsgQueueManager>;MsgQueueManager(const std::string &dbfile): _mapper(dbfile){_mqp = _mapper.recovery();}bool declareMsgQueue(const std::string &qname, bool qdurable, bool qexclusive, bool qauto_delete,const google::protobuf::Map<std::string, std::string> &qargs){std::unique_lock<std::mutex> ulock(_mutex);if (_mqp.count(qname))return true;MsgQueue::ptr mp = std::make_shared<MsgQueue>(qname, qdurable, qexclusive, qauto_delete, qargs);if (qdurable){if (!_mapper.insert(mp)){default_error("声明队列持久化数据失败, 队列名: %s",qname.c_str());return false;}}_mqp.insert(std::make_pair(qname, mp));return true;}bool eraseMsgQueue(const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);auto iter = _mqp.find(qname);if (iter == _mqp.end())return true;MsgQueue::ptr mp = iter->second;if (mp->durable){if (!_mapper.erase(mp->name)){default_error("删除队列持久化数据失败, 队列名: %s",mp->name.c_str());return false;}}_mqp.erase(iter);return true;}MsgQueueMap getAllMsgQueue(){std::unique_lock<std::mutex> ulock(_mutex);return _mqp;}MsgQueue::ptr getMsgQueue(const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);auto iter = _mqp.find(qname);if (iter == _mqp.end())return MsgQueue::ptr();return iter->second;}void clear(){std::unique_lock<std::mutex> ulock(_mutex);_mapper.dropTable();_mqp.clear();}bool exists(const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);return _mqp.count(qname) > 0;}private:std::mutex _mutex;MsgQueueMap _mqp;MsgQueueMapper _mapper;
};

没啥好说的,跟交换机一样的

5.测试代码

#include "../mqserver/msgqueue.hpp"
#include <gtest/gtest.h>using namespace ns_mq;MsgQueueManager::ptr mqmp;class MsgQueueTest : public testing::Environment
{
public:virtual void SetUp(){mqmp = std::make_shared<MsgQueueManager>("test.db");}virtual void TearDown(){mqmp->clear();}
};TEST(msgqueue_test, recovery_test)
{ASSERT_EQ(mqmp->exists("queue1"), true);ASSERT_EQ(mqmp->exists("queue2"), true);ASSERT_EQ(mqmp->exists("queue3"), false);ASSERT_EQ(mqmp->exists("queue4"), false);
}TEST(msgqueue_test, insert_test)
{google::protobuf::Map<std::string, std::string> mymap;mymap.insert({"k1", "v1"});mymap.insert({"k2", "v2"});mqmp->declareMsgQueue("queue1", true, false, false, mymap);mqmp->declareMsgQueue("queue2", true, false, false, mymap);mqmp->declareMsgQueue("queue3", true, false, false, mymap);mqmp->declareMsgQueue("queue4", false, true, true, mymap);ASSERT_EQ(mqmp->exists("queue1"), true);ASSERT_EQ(mqmp->exists("queue2"), true);ASSERT_EQ(mqmp->exists("queue3"), true);ASSERT_EQ(mqmp->exists("queue4"), true);
}TEST(msgqueue_test, select_test)
{MsgQueue::ptr mqp = mqmp->getMsgQueue("queue4");ASSERT_EQ(mqp->durable, false);ASSERT_EQ(mqp->exclusive, true);ASSERT_EQ(mqp->auto_delete, true);
}TEST(msgqueue_test, erase_test)
{mqmp->eraseMsgQueue("queue3");ASSERT_EQ(mqmp->exists("queue1"), true);ASSERT_EQ(mqmp->exists("queue2"), true);ASSERT_EQ(mqmp->exists("queue3"), false);ASSERT_EQ(mqmp->exists("queue4"), true);
}int main(int argc, char *argv[])
{testing::AddGlobalTestEnvironment(new MsgQueueTest);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

四、绑定信息管理模块的实现

1.功能需求

依然是这样:

  1. Binding
    - 【1】 交换机名称
    - 【2】 队列名称
    - 【3】 binding_key
    - 【4】持久化标志

  2. BindingMapper

    1. 对外接口
      • 【1】 创建/删除Binding表
      • 【2】 新增Binding数据
      • 【3】 持久化信息恢复
      • 【4】删除指定Binding数据
      • 【5】删除某个交换机对应的Binding数据
      • 【6】删除某个队列对应的Binding数据
    2. 成员
      -【1】 SqliteHelper句柄
  3. BindingManager

    1. 对外接口
      【1】新增Binding数据
      【2】删除指定Binding数据
      【3】删除某个交换机对应的Binding数据
      【4】删除某个队列对应的Binding数据
      【5】获取指定Binding数据
      【6】获取某个交换机对应的所有Binding数据【因为交换机收到消息之后要根据绑定信息进行消息的分发】
    2. 成员
      【1】互斥锁
      【2】<交换机名称,交换机对应的所有绑定信息>的哈希表
      【3】BindingMapper句柄
      注意:
  4. 删除交换机时要删除该交换机对应的所有Binding数据

  5. 删除队列时也要删除该队列对应的所有Binding数据

2.Binding结构体实现

struct Binding
{using ptr = std::shared_ptr<Binding>;Binding() = default;Binding(const std::string &ename, const std::string &qname, const std::string &Binding_key, bool Durable): exchange_name(ename), queue_name(qname), binding_key(Binding_key), durable(Durable) {}std::string exchange_name;std::string queue_name;std::string binding_key;bool durable;
};

3.版本一

在这里插入图片描述

1.绑定信息持久化管理类

我要维护两个哈希表~
交换机的哈希表是用来拿到交换机的所有绑定信息从而进行消息路由的
队列的哈希表是用来删除队列的所有绑定信息的

如果没有队列的哈希表,那么删除队列所有的绑定信息时就需要遍历整个交换机哈希表的所有Binding::ptr

using BindingMap = std::unordered_map<std::string, std::vector<Binding::ptr>>;class BindingMapper
{using SelectCallback = int (*)(void *, int, char **, char **);public:BindingMapper(const std::string &dbfile): _helper(dbfile){if (!_helper.open()){default_fatal("绑定信息持久化管理类初始化失败,因为数据库打开失败");abort();}if (!createTable()){default_fatal("绑定信息持久化管理类初始化失败,因为创建表失败");abort();}}bool createTable(){static std::string create_sql = "create table if not 	exists binding_table(\exchange_name varchar(32),\queue_name varchar(32),\binding_key varchar(32),\durable int);";if (!_helper.exec(create_sql, nullptr, nullptr)){default_fatal("持久化管理模块建表失败");return false;}return true;}bool dropTable(){static std::string drop_sql = "drop table if exists binding_table;";if (!_helper.exec(drop_sql, nullptr, nullptr)){default_error("绑定信息持久化管理类删除表失败");return false;}return true;}bool insert(const Binding::ptr &bp){std::ostringstream insert_sql;insert_sql << "insert into binding_table values (";insert_sql << "'" << bp->exchange_name << "',";insert_sql << "'" << bp->queue_name << "',";insert_sql << "'" << bp->binding_key << "',";insert_sql << bp->durable << ");";if (!_helper.exec(insert_sql.str(), nullptr, nullptr)){default_error("新增持久化绑定信息失败,交换机名称:%s , 队列名称:%s",bp->exchange_name.c_str(),bp->queue_name.c_str());return false;}return true;}bool erase(const std::string &ename, const std::string &qname){std::ostringstream delete_sql;delete_sql << "delete from binding_table where exchange_name=";delete_sql << "'" << ename << "' and queue_name=";delete_sql << "'" << qname << "';";if (!_helper.exec(delete_sql.str(), nullptr, nullptr)){default_error("删除指定持久化绑定信息失败,交换机名称:%s , 队列名称:%s",ename.c_str(),qname.c_str());return false;}return true;}bool removeExchangeBinding(const std::string &ename){return _removeBinding("exchange_name", ename);}bool removeMsgQueueBinding(const std::string &qname){return _removeBinding("queue_name", qname);}BindingMap recoveryExchangeBinding(){return _recoveryBinding(&BindingMapper::SelectExchangeBindingCallback, "查询交换机对应持久化绑定信息失败");}BindingMap recoveryMsgQueueBinding(){return _recoveryBinding(&BindingMapper::SelectMsgQueueBindingCallback, "查询队列对应持久化绑定信息失败");}// 重构部分的公共代码
private:static int _selectCallback(void *arg, char **rows, const std::string &name){BindingMap *bmptr = static_cast<BindingMap *>(arg);std::vector<Binding::ptr> &vec = (*bmptr)[name]; //[]不存在则创建 一定要用引用,这样写代码可读性更好Binding::ptr bp = std::make_shared<Binding>();bp->exchange_name = rows[0];bp->queue_name = rows[1];bp->binding_key = rows[2];bp->durable = static_cast<bool>(std::stoi(rows[3]));vec.push_back(bp);return 0;}bool _removeBinding(const std::string &colname, const std::string name){std::ostringstream remove_sql;remove_sql << "delete from binding_table where " << colname << "=";remove_sql << "'" << name << "';";if (!_helper.exec(remove_sql.str(), nullptr, nullptr)){default_error("删除持久化绑定信息失败,%s :%s",colname.c_str(),name.c_str());return false;}return true;}BindingMap _recoveryBinding(SelectCallback cb, const std::string &errmsg = ""){static std::string select_sql = "select * from binding_table;";BindingMap bmp;if (!_helper.exec(select_sql, cb, &bmp)){default_error("%s",errmsg.c_str());return BindingMap();}return bmp;}// 回调函数代码
private:static int SelectExchangeBindingCallback(void *arg, int column, char **rows, char **fields){return _selectCallback(arg, rows, rows[0]); // rows[0]是交换机名称}static int SelectMsgQueueBindingCallback(void *arg, int column, char **rows, char **fields){return _selectCallback(arg, rows, rows[1]); // rows[1]是队列名称}SqliteHelper _helper;
};

这其实就是SQL语句编写,然后又重构了一下而已

2.绑定信息管理类

class BindingManager
{
public:BindingManager() = default;using ptr = std::shared_ptr<BindingManager>;BindingManager(const std::string &dbfile): _mapper(dbfile){_exchange_map = _mapper.recoveryExchangeBinding();_queue_map = _mapper.recoveryMsgQueueBinding();}bool bind(const std::string &ename, const std::string &qname, const std::string &binding_key, bool durable){std::unique_lock<std::mutex> ulock(_mutex);// 1. 查找是否存在// 我们只需要在交换机这里检查是否存在即可,因为我们到时候路由的时候也是只根据交换机的哈希表进行路由的auto iter = _exchange_map.find(ename);if (iter != _exchange_map.end()){std::vector<Binding::ptr> &vec = iter->second;for (auto &bp : vec){if (bp->queue_name == qname){return true;}}}// 2. 构建bpBinding::ptr bp = std::make_shared<Binding>(ename, qname, binding_key, durable);// 3. 看是否需要持久化(因为只有当交换机持久化了+队列持久化了// 我们的绑定信息持久化才有意义,而我们不想增加模块间的耦合度,因此在虚拟机模块处理这个事情)if (durable){if (!_mapper.insert(bp)){default_error("绑定信息持久化失败: exchange_name: %s , queue_name: %s",ename.c_str(),qname.c_str());return false;}}// 4. push_back它_exchange_map[ename].push_back(bp);_queue_map[qname].push_back(bp);return true;}bool unBind(const std::string &ename, const std::string &qname){// 0. 加锁std::unique_lock<std::mutex> ulock(_mutex);// 1. 查找是否存在auto iter = _exchange_map.find(ename);if (iter == _exchange_map.end())return true;// 2. 从交换机当中删除bool ok = false;std::vector<Binding::ptr> &evec = iter->second;for (auto iter_bp = evec.begin(); iter_bp != evec.end(); ++iter_bp){Binding::ptr bp = *iter_bp;if (bp->queue_name == qname){// 看是否需要持久化if (bp->durable){if (!_mapper.erase(ename, qname)){default_error("解除持久化绑定信息失败");return false;}}ok = true;evec.erase(iter_bp);break;}}// 3. 从队列当中删除if (!ok)return true;std::vector<Binding::ptr> &qvec = _queue_map[qname];for (auto iter_bp = qvec.begin(); iter_bp != qvec.end(); ++iter_bp){Binding::ptr bp = *iter_bp;if (bp->exchange_name == ename){qvec.erase(iter_bp);break;}}return true;}std::vector<Binding::ptr> getExchangeBindings(const std::string &ename){std::unique_lock<std::mutex> ulock(_mutex);auto iter = _exchange_map.find(ename);if (iter != _exchange_map.end()){return iter->second;}return std::vector<Binding::ptr>();}Binding::ptr getBinding(const std::string &ename, const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);auto iter = _exchange_map.find(ename);if (iter != _exchange_map.end()){std::vector<Binding::ptr> &vec = iter->second;for (auto &bp : vec){if (bp->queue_name == qname){return bp;}}}return Binding::ptr();}bool exists(const std::string &ename, const std::string &qname){// 0. 加锁std::unique_lock<std::mutex> ulock(_mutex);// 1. 查找是否存在// 只要交换机哈希表当中有这个绑定信息,此时我们认为该绑定信息存在,因为交换机进行数据分发时,看的是交换机哈希表上的绑定信息auto iter = _exchange_map.find(ename);if (iter != _exchange_map.end()){std::vector<Binding::ptr> &vec = iter->second;for (auto &bp : vec){if (bp->queue_name == qname){return true;}}}return false;}bool removeExchangeBindings(const std::string &ename){std::unique_lock<std::mutex> ulock(_mutex);auto iter = _exchange_map.find(ename);if (iter != _exchange_map.end()){// 1. 从表中删除if (!_mapper.removeExchangeBinding(ename)){default_error("移除该交换机所有持久化绑定信息失败");return false;}std::vector<Binding::ptr> &evec = iter->second;for (auto &bp : evec){std::string qname = bp->queue_name;std::vector<Binding::ptr> &qvec = _queue_map[qname];// 小心迭代器失效问题for (auto it = qvec.begin(); it != qvec.end();){if ((*it)->exchange_name == ename){qvec.erase(it);}else++it;}}_exchange_map.erase(iter);return true;}return true;}bool removeMsgQueueBindings(const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);auto iter = _queue_map.find(qname);if (iter != _queue_map.end()){// 1. 从表中删除if (!_mapper.removeMsgQueueBinding(qname)){default_error("移除该队列所有持久化绑定信息失败");return false;}std::vector<Binding::ptr> &qvec = iter->second;for (auto &bp : qvec){std::string ename = bp->exchange_name;std::vector<Binding::ptr> &evec = _exchange_map[ename];// 小心迭代器失效问题for (auto it = evec.begin(); it != evec.end();){if ((*it)->queue_name == qname){evec.erase(it);}else++it;}}_queue_map.erase(iter);return true;}return true;}void clear(){std::unique_lock<std::mutex> ulock(_mutex);_mapper.dropTable();_exchange_map.clear();_queue_map.clear();}private:std::mutex _mutex;BindingMapper _mapper;BindingMap _exchange_map;BindingMap _queue_map;
};

仔细看的话,其实也没啥难的,就是STL容器的使用,写的时候注意力集中一点,仔细一点就问题不大

缺点是删除的效率实在是太低了
原因:Binding::ptr在每个BindingMap当中都存在  且  两个哈希表没有建立联系

4.版本二

在这里插入图片描述
在这里插入图片描述

using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;

1.绑定信息持久化管理类

class BindingMapper
{using SelectCallback = int (*)(void *, int, char **, char **);public:BindingMapper(const std::string &dbfile): _helper(dbfile){if (!_helper.open()){default_fatal("绑定信息持久化管理类初始化失败,因为数据库打开失败");abort();}if (!createTable()){default_fatal("绑定信息持久化管理类初始化失败,因为创建表失败");abort();}}bool createTable(){static std::string create_sql = "create table if not exists binding_table(\exchange_name varchar(32),\queue_name varchar(32),\binding_key varchar(32),\durable int);";if (!_helper.exec(create_sql, nullptr, nullptr)){default_fatal("持久化管理模块建表失败");return false;}return true;}bool dropTable(){static std::string drop_sql = "drop table if exists binding_table;";if (!_helper.exec(drop_sql, nullptr, nullptr)){default_error("绑定信息持久化管理类删除表失败");return false;}return true;}bool insert(const Binding::ptr &bp){std::ostringstream insert_sql;insert_sql << "insert into binding_table values (";insert_sql << "'" << bp->exchange_name << "',";insert_sql << "'" << bp->queue_name << "',";insert_sql << "'" << bp->binding_key << "',";insert_sql << bp->durable << ");";if (!_helper.exec(insert_sql.str(), nullptr, nullptr)){default_error("新增持久化绑定信息失败,交换机名称:%s , 队列名称:%s",bp->exchange_name.c_str(),bp->queue_name.c_str());return false;}return true;}bool erase(const std::string &ename, const std::string &qname){std::ostringstream delete_sql;delete_sql << "delete from binding_table where exchange_name=";delete_sql << "'" << ename << "' and queue_name=";delete_sql << "'" << qname << "';";if (!_helper.exec(delete_sql.str(), nullptr, nullptr)){default_error("删除指定持久化绑定信息失败,交换机名称:%s , 队列名称:%s",ename.c_str(),qname.c_str());return false;}return true;}bool removeExchangeBinding(const std::string &ename){return _removeBinding("exchange_name", ename);}bool removeMsgQueueBinding(const std::string &qname){return _removeBinding("queue_name", qname);}BindingMap recoveryExchangeBinding(){static std::string select_sql = "select * from binding_table;";BindingMap bmp;if (!_helper.exec(select_sql, &BindingMapper::SelectExchangeBindingCallback, &bmp)){default_error("查询交换机对应持久化绑定信息失败");return BindingMap();}return bmp;}// 重构部分的公共代码
private:bool _removeBinding(const std::string &colname, const std::string name){std::ostringstream remove_sql;remove_sql << "delete from binding_table where " << colname << "=";remove_sql << "'" << name << "';";if (!_helper.exec(remove_sql.str(), nullptr, nullptr)){default_error("删除持久化绑定信息失败,%s : %s",colname.c_str(),name.c_str());return false;}return true;}// 回调函数代码
private:static int SelectExchangeBindingCallback(void *arg, int column, char **rows, char **fields){BindingMap *bmptr = static_cast<BindingMap *>(arg);MsgQueueBindingMap &mqbm = (*bmptr)[rows[0]]; //[]不存在则创建 一定要用引用,这样写代码可读性更好Binding::ptr bp = std::make_shared<Binding>();bp->exchange_name = rows[0];bp->queue_name = rows[1];bp->binding_key = rows[2];bp->durable = static_cast<bool>(std::stoi(rows[3]));mqbm.insert(std::make_pair(bp->queue_name, bp));return 0;}SqliteHelper _helper;
};

这个比上个版本简单不少

2.绑定信息管理类

class BindingManager
{
public:BindingManager() = default;using ptr = std::shared_ptr<BindingManager>;BindingManager(const std::string &dbfile): _mapper(dbfile){_exchange_map = _mapper.recoveryExchangeBinding();}bool bind(const std::string &ename, const std::string &qname, const std::string &binding_key, bool durable){// 0. 加锁std::unique_lock<std::mutex> ulock(_mutex);// 1. 查找是否存在MsgQueueBindingMap &mqbm = _exchange_map[ename];if (mqbm.count(qname))return true;// 2. 构建bpBinding::ptr bp = std::make_shared<Binding>(ename, qname, binding_key, durable);// 3. 看是否持久化if (durable){if (!_mapper.insert(bp)){default_error("绑定信息持久化失败,交换机名称:%s , 队列名称:%s",ename.c_str(),qname.c_str());return false;}}// 4. 直接插入mqbm.insert(std::make_pair(qname, bp));return true;}bool unBind(const std::string &ename, const std::string &qname){// 0. 加锁std::unique_lock<std::mutex> ulock(_mutex);// 1. 查找auto eiter = _exchange_map.find(ename);if (eiter == _exchange_map.end())return true;MsgQueueBindingMap &mqbm = eiter->second;auto qiter = mqbm.find(qname);if (qiter == mqbm.end())return true;// 2. 看是否持久化Binding::ptr bp = qiter->second;if (bp->durable){if (!_mapper.erase(ename, qname)){default_error("解绑失败,交换机名称:%s ,队列名称:%s",ename.c_str(),qname.c_str());return false;}}// 3. 直接删除mqbm.erase(qname);return true;}MsgQueueBindingMap getExchangeBindings(const std::string &ename){std::unique_lock<std::mutex> ulock(_mutex);return _exchange_map[ename];}Binding::ptr getBinding(const std::string &ename, const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);MsgQueueBindingMap &mqbm = _exchange_map[ename];auto iter = mqbm.find(qname);if (iter == mqbm.end())return Binding::ptr();return iter->second;}bool exists(const std::string &ename, const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);MsgQueueBindingMap &mqbm = _exchange_map[ename];auto iter = mqbm.find(qname);if (iter == mqbm.end())return false;return true;}bool removeExchangeBindings(const std::string &ename){std::unique_lock<std::mutex> ulock(_mutex);if (!_mapper.removeExchangeBinding(ename)){default_error("删除交换机表失败");return false;}_exchange_map.erase(ename); // 因为队列那张表是依附于交换机这样表而存在的,所以我们直接暴力删表即可return true;}bool removeMsgQueueBindings(const std::string &qname){std::unique_lock<std::mutex> ulock(_mutex);if (!_mapper.removeMsgQueueBinding(qname)){default_error("删除队列表失败");return false;}// 遍历整张交换机表,删除队列信息for (auto &kv : _exchange_map){MsgQueueBindingMap &mqbm = kv.second;mqbm.erase(qname);}return true;}void clear(){std::unique_lock<std::mutex> ulock(_mutex);_mapper.dropTable();_exchange_map.clear();}private:std::mutex _mutex;BindingMapper _mapper;BindingMap _exchange_map;
};

这个效率就杠杠的了,唯一有点削微慢一丝丝的就是这个删除某个队列所有绑定信息时要遍历整个交换机表,但是因为MsgQueueBindingMap是哈希表,所以效率也挺高的

比版本快太多了

5.测试

#include "../mqserver/binding_upper_version.hpp"
#include <gtest/gtest.h>
using namespace ns_mq;BindingManager::ptr bmp;class BindingTest : public testing::Environment
{
public:virtual void SetUp(){bmp = std::make_shared<BindingManager>("test.db");}virtual void TearDown(){bmp->clear();}
};TEST(binding_test, recovery_test)
{ASSERT_EQ(bmp->exists("exchange1", "queue1"), false);ASSERT_EQ(bmp->exists("exchange1", "queue2"), false);ASSERT_EQ(bmp->exists("exchange1", "queue3"), false);ASSERT_EQ(bmp->exists("exchange2", "queue1"), false);ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);
}TEST(binding_test, insert_test)
{ASSERT_NE(bmp.get(), nullptr);bmp->bind("exchange1", "queue1", "news.sport.basketball", true);bmp->bind("exchange1", "queue2", "news.sport.basketball", true);bmp->bind("exchange1", "queue3", "news.sport.basketball", true);bmp->bind("exchange2", "queue1", "news.music.pop", true);bmp->bind("exchange2", "queue2", "news.music.pure", true);bmp->bind("exchange2", "queue3", "news.music.#", true);ASSERT_EQ(bmp->exists("exchange1", "queue1"), true);ASSERT_EQ(bmp->exists("exchange1", "queue2"), true);ASSERT_EQ(bmp->exists("exchange1", "queue3"), true);ASSERT_EQ(bmp->exists("exchange2", "queue1"), true);ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);
}TEST(binding_test, select_test)
{Binding::ptr bp = bmp->getBinding("exchange2", "queue2");ASSERT_NE(bp.get(), nullptr);ASSERT_EQ(bp->binding_key, std::string("news.music.pure"));auto bm = bmp->getExchangeBindings("exchange1");ASSERT_EQ(bm.size(), 3);
}TEST(binding_test, erase_test)
{bmp->removeMsgQueueBindings("queue1");ASSERT_EQ(bmp->exists("exchange1", "queue1"), false);ASSERT_EQ(bmp->exists("exchange1", "queue2"), true);ASSERT_EQ(bmp->exists("exchange1", "queue3"), true);ASSERT_EQ(bmp->exists("exchange2", "queue1"), false);ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);bmp->removeExchangeBindings("exchange1");ASSERT_EQ(bmp->exists("exchange1", "queue1"), false);ASSERT_EQ(bmp->exists("exchange1", "queue2"), false);ASSERT_EQ(bmp->exists("exchange1", "queue3"), false);ASSERT_EQ(bmp->exists("exchange2", "queue1"), false);ASSERT_EQ(bmp->exists("exchange2", "queue2"), true);ASSERT_EQ(bmp->exists("exchange2", "queue3"), true);
}int main(int argc, char *argv[])
{testing::AddGlobalTestEnvironment(new BindingTest);testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}

以上就是项目第四弹:交换机、队列、绑定信息管理模块分析与代码实现的全部内容

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

查找算法 01分块查找

自己设计一个分块查找的例子&#xff0c;不少于15个数据元素&#xff0c;并建立分块查找的索引 基于上述例子&#xff0c;计算查找成功的ASL、查找失败的ASL 拓展&#xff1a; ‌‌分块查找的平均查找长度&#xff08;‌ASL&#xff09;的计算公式如下‌&#xff1a;‌ ‌顺序…

ESP32 JTAG 调试

前言 个人邮箱&#xff1a;zhangyixu02gmail.com本人使用的是 Ubuntu 环境&#xff0c;采用 GDB 方式进行调试。对于新手&#xff0c;我个人还是建议参考ESP32S3学习笔记&#xff08;0&#xff09;—— Vscode IDF环境搭建及OpenOCD调试介绍进行图形化的方式调试。如果是希望在…

占领矩阵-第15届蓝桥省赛Scratch中级组真题第5题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第190讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

Python酷库之旅-第三方库Pandas(122)

目录 一、用法精讲 541、pandas.DataFrame.take方法 541-1、语法 541-2、参数 541-3、功能 541-4、返回值 541-5、说明 541-6、用法 541-6-1、数据准备 541-6-2、代码示例 541-6-3、结果输出 542、pandas.DataFrame.truncate方法 542-1、语法 542-2、参数 542-3…

植保无人机是朝阳产业还是夕阳产业?

植保无人机产业是朝阳产业还是夕阳产业&#xff0c;可以从多个维度进行分析&#xff1a; 一、市场需求与增长趋势 市场需求&#xff1a;随着农业现代化的推进和劳动力成本的上升&#xff0c;植保无人机因其高效、安全、节省农药等优势&#xff0c;在农业生产中的应用越来越广…

自闭症能上寄宿学校吗?了解解答与选择

在探讨自闭症儿童教育的话题时&#xff0c;寄宿学校作为一种特殊的教育模式&#xff0c;常常引发家长们的关注与讨论。对于自闭症儿童而言&#xff0c;寄宿学校既是一个充满挑战的新环境&#xff0c;也是一个能够促进他们独立成长与社交融合的重要平台。今天&#xff0c;我们将…

自制数据库空洞率清理工具-C版-03-EasyClean-V1.3(支持南大通用数据库Gbase8a)

目录 一、环境信息 二、简述 三、升级点 四、支持功能 五、空洞率 六、工具流程图 1、流程描述 2、注意点 &#xff08;1&#xff09;方法一 &#xff08;2&#xff09;方法二 七、清理空洞率流程图 八、安装包下载地址 九、参数介绍 1、命令模板 2、命令样例 3…

【C语言-数据结构】单链表的定义

单链表的定义&#xff08;实现&#xff09; 比较顺序表和单链表的物理存储结构就能够清楚地发现二者的区别 用代码定义一个单链表 typedef struct LNode{ElemType data; //每个结点存放一个数据元素struct LNode* next; //指针指向下一个结点 }LNode, *LinkList;//要表示一个…

[JavaEE] TCP协议

目录 一、TCP协议段格式 二、TCP确保传输可靠的机制 2.1 确认应答 2.2 超时重传 2.3 连接管理 2.3.1 三次握手 2.3.2 四次挥手 2.4 滑动窗口 2.4.1 基础知识 2.4.2 两种丢包情况 2.4.2.1 数据报已经抵达&#xff0c;ACK丢包 2.4.2.2 数据包丢包 2.5 流量控制…

【时时三省】(C语言基础)指针笔试题2

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 笔试题2 这里的0x1是16进制的1 跟十进制的1一样 这道题考察的是&#xff1a;指针类型决定了指针的运算 p是上面结构体的指针 它指向的大小结果是20个字节 指针…

项目第五弹:队列消息管理模块

项目第五弹&#xff1a;队列消息管理模块 一、消息如何组织并管理1.消息结构体2.消息持久化管理模块设计1.数据消息文件名2.临时消息文件名3.对外接口与包含成员 二、自定义应用层协议解决文件读写的粘包问题1.Length-Value协议 三、队列消息管理模块设计1.待确认消息哈希表2.待…

[数据结构]动态顺序表的实现与应用

文章目录 一、引言二、动态顺序表的基本概念三、动态顺序表的实现1、结构体定义2、初始化3、销毁4、扩容5、缩容5、打印6、增删查改 四、分析动态顺序表1、存储方式2、优点3、缺点 五、总结1、练习题2、源代码 一、引言 想象一下&#xff0c;你有一个箱子&#xff08;静态顺序…

【医学半监督】对比互补掩蔽的自监督预训练半监督心脏图像分割

SELF-SUPERVISED PRE-TRAINING BASED ON CONTRASTIVE COMPLEMENTARY MASKING FOR SEMI-SUPERVISED CARDIAC IMAGE SEGMENTATION 2024 IEEE International Symposium on Biomedical Imaging (ISBI) 摘要: 心脏结构分割对心脏病诊断非常重要,而使用大量注释的深度学习在这项任…

Buck变换器闭环控制,simulink仿真模型(适合初学者学习)

Buck变换器&#xff0c;又称为降压斩波器&#xff0c;是一种常见的DC-DC转换器&#xff0c;广泛应用于电源管理领域。它通过开关元件&#xff08;通常是MOSFET或BJT&#xff09;的导通与截止&#xff0c;改变输入电压到负载的平均电压&#xff0c;从而实现电压的降低。在实际应…

harbor私有镜像仓库,搭建及管理

私有镜像仓库 docker-distribution docker的镜像仓库&#xff0c;默认端口号5000 做个仓库&#xff0c;把镜像放里头&#xff0c;用什么服务&#xff0c;起什么容器 vmware公司在docker私有仓库的基础上做了一个web页面&#xff0c;是harbor docker可以把仓库的镜像下载到本地&…

tauri嵌入自定义目录/文件,并在代码中读取文件内容的操作流程

可以看官方文档&#xff1a;Embedding Additional Files | Tauri Apps 在绑定了文件之后&#xff0c;可以在js中访问嵌入的文件或者在rust中读取嵌入的文件内容&#xff0c;详细的配置操作如下。 在src-tauri中创建自定义文件夹或文件&#xff0c;并在在tauri.conf.json中配置…

Java多线程Thread及其原理深度解析

文章目录 1. 实现多线程的方式2. Thread 部分源码2.1. native 方法注册2.2. Thread 中的成员变量2.3. Thread 构造方法与初始化2.4. Thread 线程状态与操作系统状态2.4. start() 与 run() 方法2.5. sleep() 方法2.6. join() 方法2.7. interrupt() 方法 本文参考&#xff1a; 线…

Spring自定义参数解析器

在这篇文章中&#xff0c;我们认识了参数解析器和消息转换器&#xff0c;今天我们来自定义一个参数解析器。 自定义参数解析器 实现HandlerMethodArgumentResolver的类&#xff0c;并注册到Spring容器。 Component&#xff0f;&#xff0f;注册到Spring public class UserAr…

Java集合必知必会:热门面试题汇编与核心源码(ArrayList、HashMap)剖析

写在前面 &#x1f525;我把后端Java面试题做了一个汇总&#xff0c;有兴趣大家可以看看&#xff01;这里&#x1f449; ⭐️在无数次的复习巩固中&#xff0c;我逐渐意识到一个问题&#xff1a;面对同样的面试题目&#xff0c;不同的资料来源往往给出了五花八门的解释&#…

【Linux进程控制】自主Shell

目录 自主shell实现 获取基本变量 实现命令行 获取用户命令字符串 命令行字符串分割 内建命令CD() chdir getcwd putenv 检查是否为内建命令 检查是否为重定向 执行命令 主函数设置 测试用例 项目代码 自主shell实现 根据之前学的内容&#xff0c;我们已经可以模…