1. 虚拟机管理
虚拟机模块是对持久化数据管理中心模块当中的四个数据管理模块的整合,并基于数据之间的关联关系进行联合操作。
- 定义虚拟机类包含以下成员:
- 交换机数据管理模块句柄
- 队列数据管理模块句柄
- 绑定数据管理模块句柄
- 消息数据管理模块句柄
- 虚拟机包含操作:
- 提供声明交换机的功能(存在则 OK,不存在则创建)
- 提供删除交换机的功能(删除交换机的同时删除关联绑定信息)
- 提供声明队列的功能(存在则 OK,不存在则创建,创建的同时创建队列关联消息管理对象)
- 提供删除队列的功能(删除队列的同时删除关联绑定信息,删除关联消息管理对象及队列所有消息)
- 提供交换机-队列绑定的功能
- 提供交换机-队列解绑的功能
- 提供获取交换机相关的所有绑定信息功能
- 提供新增消息的功能
- 提供获取指定队列队首消息的功能
- 提供消息确认删除的功能
- 虚拟机管理操作:
- 增删查(该项目中做了简化,只提供一个虚拟机的操作,第3点可作为扩展功能去实现)
cpp
#ifndef __M_HOST_H__
#define __M_HOST_H__
#include "exchange.hpp"
#include "queue.hpp"
#include "binding.hpp"
#include "message.hpp"
namespace rabbitmq
{
class VirtualHost
{
public:
using ptr = std::shared_ptr<VirtualHost>;
VirtualHost(const std::string& hostname, const std::string& basedir, const std::string& dbfile)
:_host_name(hostname)
,_emp(std::make_shared<ExchangeManager>(dbfile))
,_mqmp(std::make_shared<MsgQueueManager>(dbfile))
,_bmp(std::make_shared<BindingManager>(dbfile))
,_mmp(std::make_shared<MessageManager>(basedir))
{
//获取到所有的队列信息,通过队列名称恢复历史消息数据
QueueMap qm = _mqmp->allQueues();
for(auto& q : qm)
{
_mmp->initQueueMessage(q.first);
}
}
//声明交换机(存在则 OK,不存在则创建)
bool declareExchange(const std::string &name,
ExchangeType type, bool durable, bool auto_delete,
const google::protobuf::Map<std::string, std::string> &args)
{
return _emp->declareExchange(name, type, durable, auto_delete, args);
}
//删除交换机(删除交换机的同时删除关联绑定信息)
void deleteExchange(const std::string &name)
{
//删除交换机的时候,需要将交换机相关的绑定信息也删除掉。
_bmp->removeExchangeBindings(name);
_emp->deleteExchange(name);
}
// 判断指定交换机是否存在
bool existsExchange(const std::string &name)
{
return _emp->exists(name);
}
// 查询指定交换机对象
Exchange::ptr selectExchange(const std::string &ename)
{
return _emp->selectExchange(ename);
}
// 声明队列(存在则 OK,不存在则创建,创建的同时创建队列关联消息管理对象)
bool declareQueue(const std::string &qname,
bool qdurable,
bool qexclusive,
bool qauto_delete,
const google::protobuf::Map<std::string, std::string> &qargs)
{
//初始化队列的消息句柄(消息的存储管理)
//队列的创建
_mmp->initQueueMessage(qname);
return _mqmp->declareQueue(qname, qdurable, qexclusive, qauto_delete, qargs);
}
//删除队列(删除队列的同时删除关联绑定信息,删除关联消息管理对象及队列所有消息)
void deleteQueue(const std::string &name)
{
_mmp->destroyQueueMessage(name);
_bmp->removeMsgQueueBindings(name);
_mqmp->deleteQueue(name);
}
// 判断指定队列是否存在
bool existsQueue(const std::string &name)
{
return _mqmp->exists(name);
}
QueueMap allQueues()
{
return _mqmp->allQueues();
}
// 交换机-队列绑定
bool bind(const std::string &ename, const std::string &qname, const std::string &key)
{
Exchange::ptr ep = _emp->selectExchange(ename);
if (ep.get() == nullptr)
{
DLOG("进行队列绑定失败,交换机%s不存在!", ename.c_str());
return false;
}
MsgQueue::ptr mqp = _mqmp->selectQueue(qname);
if (mqp.get() == nullptr)
{
DLOG("进行队列绑定失败,队列%s不存在!", qname.c_str());
return false;
}
return _bmp->bind(ename, qname, key, ep->_durable && mqp->_durable);
}
// 交换机-队列解绑
void unBind(const std::string &ename, const std::string &qname)
{
_bmp->unBind(ename, qname);
}
// 获取交换机相关的所有绑定信息
MsgQueueBindingMap exchangeBindings(const std::string &ename)
{
return _bmp->getExchangeBindings(ename);
}
bool existsBinding(const std::string &ename, const std::string &qname)
{
return _bmp->exists(ename, qname);
}
// 新增消息
bool basicPublish(const std::string &qname, BasicProperties *bp, const std::string &body)
{
MsgQueue::ptr mqp = _mqmp->selectQueue(qname);
if (mqp.get() == nullptr)
{
DLOG("发布消息失败,队列%s不存在!", qname.c_str());
return false;
}
return _mmp->insert(qname, bp, body, mqp->_durable);
}
// 获取指定队列队首消息
MessagePtr basicConsume(const std::string &qname)
{
return _mmp->front(qname);
}
// 消息确认(删除)
void basicAck(const std::string &qname, const std::string &msgid)
{
_mmp->ack(qname, msgid);
}
void clear()
{
_emp->clear();
_mqmp->clear();
_bmp->clear();
_mmp->clear();
}
private:
std::string _host_name;
ExchangeManager::ptr _emp;
MsgQueueManager::ptr _mqmp;
BindingManager::ptr _bmp;
MessageManager::ptr _mmp;
};
}
#endif
一、整体架构
VirtualHost 是整个消息系统的核心协调器,它整合了四个核心组件:
cpp
ExchangeManager::ptr _emp; // 交换机管理器
MsgQueueManager::ptr _mqmp; // 队列管理器
BindingManager::ptr _bmp; // 绑定管理器
MessageManager::ptr _mmp; // 消息管理器
这四个组件协同工作,形成一个完整的消息处理流程。
二、初始化流程
- 构造函数初始化
plain
VirtualHost(hostname, basedir, dbfile):
1. 记录主机名
2. 创建四个管理器实例:
- ExchangeManager: 管理交换机(元数据持久化到dbfile)
- MsgQueueManager: 管理队列(元数据持久化到dbfile)
- BindingManager: 管理绑定关系(元数据持久化到dbfile)
- MessageManager: 管理消息(消息数据持久化到basedir)
3. 从队列管理器获取所有队列
4. 为每个队列初始化消息管理(恢复历史消息)
- 消息恢复机制
- 系统启动时自动恢复所有队列的历史消息
- 通过
initQueueMessage()触发消息的持久化恢复 - 确保系统异常重启后消息不丢失
三、交换机管理流程
- 声明交换机(declareExchange)
plain
流程:
1. 调用ExchangeManager的declareExchange方法
2. 如果交换机已存在,返回成功
3. 如果不存在,创建新交换机
4. 持久化交换机元数据到数据库
- 支持交换机的持久化设置
- 支持多种交换机类型(Direct, Fanout, Topic, Headers等)
- 删除交换机(deleteExchange)
plain
流程:
1. 删除该交换机的所有绑定关系(_bmp->removeExchangeBindings)
2. 删除交换机本身(_emp->deleteExchange)
- 级联删除:删除交换机时自动删除相关绑定
- 防止孤儿绑定
四、队列管理流程
- 声明队列(declareQueue)
plain
流程:
1. 初始化队列的消息管理器(创建持久化文件)
2. 调用队列管理器声明队列
3. 持久化队列元数据
- 消息恢复:队列声明时自动恢复历史消息
- 文件创建:为队列创建对应的消息存储文件
- 删除队列(deleteQueue)
plain
流程:
1. 销毁队列的消息管理器(删除消息文件)
2. 删除队列的所有绑定关系
3. 删除队列元数据
- 彻底清理:删除队列的所有相关资源
- 防止内存泄漏
五、绑定管理流程
- 创建绑定(bind)
plain
流程:
1. 验证交换机和队列是否存在
2. 检查交换机和队列的持久化状态
3. 创建绑定关系(如果两者都持久化,绑定也持久化)
4. 记录绑定到数据库
- 绑定持久化:只有交换机和队列都持久化时,绑定才持久化
- 路由键匹配:支持不同类型的路由匹配
- 解绑(unBind)
plain
流程:
1. 移除交换机和队列的绑定关系
2. 更新数据库中的绑定记录
- 不影响消息:解绑后已发送的消息仍在队列中
- 只影响后续的消息路由
六、消息流程(核心业务流程)
- 消息发布(basicPublish)
plain
流程:
1. 验证目标队列是否存在
2. 获取队列的持久化设置
3. 根据消息属性和队列设置决定消息是否持久化
4. 调用MessageManager插入消息
- 消息持久化:取决于队列设置和消息属性
- 消息属性:包括消息ID、路由键、持久化模式等
- 消息消费(basicConsume)
plain
流程:
1. 从队列获取队首消息
2. 消息从"待推送"状态变为"待确认"状态
3. 返回消息给消费者
- 可靠消费:消息被取走后不会立即删除
- 待确认机制:确保消息被正确处理后才删除
- 消息确认(basicAck)
plain
流程:
1. 查找待确认消息
2. 根据持久化设置更新磁盘文件
3. 从内存中删除消息
4. 可能触发垃圾回收
- 消费者显式确认
- 支持消息重传(如果未确认)
- 自动清理无效数据
2. 测试
初始化测试:
cpp
#include <gtest/gtest.h>
#include "../mqserver/host.hpp"
class HostTest : public testing::Test
{
public:
void SetUp() override
{
google::protobuf::Map<std::string, std::string> empty_map = google::protobuf::Map<std::string, std::string>();
_host = std::make_shared<rabbitmq::VirtualHost>("host1", "./data/host1/message/", "./data/host1/host1.db");
_host->declareExchange("exchange1", rabbitmq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareExchange("exchange2", rabbitmq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareExchange("exchange3", rabbitmq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareQueue("queue1", true, false, false, empty_map);
_host->declareQueue("queue2", true, false, false, empty_map);
_host->declareQueue("queue3", true, false, false, empty_map);
_host->bind("exchange1", "queue1", "news.music.#");
_host->bind("exchange1", "queue2", "news.music.#");
_host->bind("exchange1", "queue3", "news.music.#");
_host->bind("exchange2", "queue1", "news.music.#");
_host->bind("exchange2", "queue2", "news.music.#");
_host->bind("exchange2", "queue3", "news.music.#");
_host->bind("exchange3", "queue1", "news.music.#");
_host->bind("exchange3", "queue2", "news.music.#");
_host->bind("exchange3", "queue3", "news.music.#");
_host->basicPublish("queue1", nullptr, "Hello World-1");
_host->basicPublish("queue1", nullptr, "Hello World-2");
_host->basicPublish("queue1", nullptr, "Hello World-3");
_host->basicPublish("queue2", nullptr, "Hello World-1");
_host->basicPublish("queue2", nullptr, "Hello World-2");
_host->basicPublish("queue2", nullptr, "Hello World-3");
_host->basicPublish("queue3", nullptr, "Hello World-1");
_host->basicPublish("queue3", nullptr, "Hello World-2");
_host->basicPublish("queue3", nullptr, "Hello World-3");
}
void TearDown() override
{
_host->clear();
}
public:
rabbitmq::VirtualHost::ptr _host;
};
TEST_F(HostTest, init_test)
{
ASSERT_EQ(_host->existsExchange("exchange1"), true);
ASSERT_EQ(_host->existsExchange("exchange2"), true);
ASSERT_EQ(_host->existsExchange("exchange3"), true);
ASSERT_EQ(_host->existsQueue("queue1"), true);
ASSERT_EQ(_host->existsQueue("queue2"), true);
ASSERT_EQ(_host->existsQueue("queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue3"), true);
rabbitmq::MessagePtr msg1 = _host->basicConsume("queue1");
ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
rabbitmq::MessagePtr msg2 = _host->basicConsume("queue1");
ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
rabbitmq::MessagePtr msg3 = _host->basicConsume("queue1");
ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
rabbitmq::MessagePtr msg4 = _host->basicConsume("queue1");
ASSERT_EQ(msg4.get(), nullptr);
}
int main(int argc, char *argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
测试覆盖的功能点
- 交换机管理
- 交换机创建
- 交换机存在性检查
- 队列管理
- 队列创建
- 队列存在性检查
- 绑定管理
- 交换机-队列绑定
- 绑定关系验证
- 消息处理
- 消息发布
- 消息消费(FIFO顺序)
- 空队列处理
- 综合功能
- 虚拟主机初始化
- 消息持久化恢复
运行结果:
bash
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_hosttest.cpp ../mqcommon/msg.pb.cc -o mq_hosttest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_hosttest
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from HostTest
[ RUN ] HostTest.init_test
[ OK ] HostTest.init_test (42 ms)
[----------] 1 test from HostTest (42 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (42 ms total)
[ PASSED ] 1 test.
删除交换机和队列测试:
cpp
#include <gtest/gtest.h>
#include "../mqserver/host.hpp"
class HostTest : public testing::Test
{
public:
void SetUp() override
{
google::protobuf::Map<std::string, std::string> empty_map = google::protobuf::Map<std::string, std::string>();
_host = std::make_shared<rabbitmq::VirtualHost>("host1", "./data/host1/message/", "./data/host1/host1.db");
_host->declareExchange("exchange1", rabbitmq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareExchange("exchange2", rabbitmq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareExchange("exchange3", rabbitmq::ExchangeType::DIRECT, true, false, empty_map);
_host->declareQueue("queue1", true, false, false, empty_map);
_host->declareQueue("queue2", true, false, false, empty_map);
_host->declareQueue("queue3", true, false, false, empty_map);
_host->bind("exchange1", "queue1", "news.music.#");
_host->bind("exchange1", "queue2", "news.music.#");
_host->bind("exchange1", "queue3", "news.music.#");
_host->bind("exchange2", "queue1", "news.music.#");
_host->bind("exchange2", "queue2", "news.music.#");
_host->bind("exchange2", "queue3", "news.music.#");
_host->bind("exchange3", "queue1", "news.music.#");
_host->bind("exchange3", "queue2", "news.music.#");
_host->bind("exchange3", "queue3", "news.music.#");
_host->basicPublish("queue1", nullptr, "Hello World-1");
_host->basicPublish("queue1", nullptr, "Hello World-2");
_host->basicPublish("queue1", nullptr, "Hello World-3");
_host->basicPublish("queue2", nullptr, "Hello World-1");
_host->basicPublish("queue2", nullptr, "Hello World-2");
_host->basicPublish("queue2", nullptr, "Hello World-3");
_host->basicPublish("queue3", nullptr, "Hello World-1");
_host->basicPublish("queue3", nullptr, "Hello World-2");
_host->basicPublish("queue3", nullptr, "Hello World-3");
}
void TearDown() override
{
_host->clear();
}
public:
rabbitmq::VirtualHost::ptr _host;
};
TEST_F(HostTest, init_test)
{
ASSERT_EQ(_host->existsExchange("exchange1"), true);
ASSERT_EQ(_host->existsExchange("exchange2"), true);
ASSERT_EQ(_host->existsExchange("exchange3"), true);
ASSERT_EQ(_host->existsQueue("queue1"), true);
ASSERT_EQ(_host->existsQueue("queue2"), true);
ASSERT_EQ(_host->existsQueue("queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange1", "queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange2", "queue3"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue1"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue2"), true);
ASSERT_EQ(_host->existsBinding("exchange3", "queue3"), true);
rabbitmq::MessagePtr msg1 = _host->basicConsume("queue1");
ASSERT_EQ(msg1->payload().body(), std::string("Hello World-1"));
rabbitmq::MessagePtr msg2 = _host->basicConsume("queue1");
ASSERT_EQ(msg2->payload().body(), std::string("Hello World-2"));
rabbitmq::MessagePtr msg3 = _host->basicConsume("queue1");
ASSERT_EQ(msg3->payload().body(), std::string("Hello World-3"));
rabbitmq::MessagePtr msg4 = _host->basicConsume("queue1");
ASSERT_EQ(msg4.get(), nullptr);
}
TEST_F(HostTest, remove_exchange)
{
_host->deleteExchange("exchange1");
ASSERT_EQ(_host->existsBinding("exchange1", "queue1"), false);
ASSERT_EQ(_host->existsBinding("exchange1", "queue2"), false);
ASSERT_EQ(_host->existsBinding("exchange1", "queue3"), false);
}
TEST_F(HostTest, remove_queue)
{
_host->deleteQueue("queue1");
ASSERT_EQ(_host->existsBinding("exchange1", "queue1"), false);
ASSERT_EQ(_host->existsBinding("exchange2", "queue1"), false);
ASSERT_EQ(_host->existsBinding("exchange3", "queue1"), false);
rabbitmq::MessagePtr msg1 = _host->basicConsume("queue1");
ASSERT_EQ(msg1.get(), nullptr);
}
int main(int argc, char *argv[])
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
运行结果:
bash
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ make
g++ -std=c++11 mq_hosttest.cpp ../mqcommon/msg.pb.cc -o mq_hosttest -lgtest /usr/local/lib/libprotobuf.so.32 -lsqlite3
ltx@My-Xshell-8-Pro-Max-Ultra:~/rabbit-mq/mqtest$ ./mq_hosttest
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from HostTest
[ RUN ] HostTest.init_test
[ OK ] HostTest.init_test (41 ms)
[ RUN ] HostTest.remove_exchange
[ OK ] HostTest.remove_exchange (43 ms)
[ RUN ] HostTest.remove_queue
[DBG][17:24:50][../mqserver/message.hpp:452] 获取队列queue1队首消息失败: 没有找到消息管理句柄!
[ OK ] HostTest.remove_queue (43 ms)
[----------] 3 tests from HostTest (127 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (127 ms total)
[ PASSED ] 3 tests.
这里有一条错误日志,是因为我们将队列queue1删除了,那此时我们再消费queue1中的消息,肯定会出错。因此是没有问题的