目录
[1. DeliverMode 枚举](#1. DeliverMode 枚举)
[2. BasicAttributes 消息属性](#2. BasicAttributes 消息属性)
[3. Message 消息](#3. Message 消息)
[3.1 MessageSelf 内部消息结构](#3.1 MessageSelf 内部消息结构)
[3.2 Message 结构](#3.2 Message 结构)
[1. 构造函数](#1. 构造函数)
[2. 插入消息](#2. 插入消息)
[3. 删除消息](#3. 删除消息)
[4. 垃圾回收gc](#4. 垃圾回收gc)
[5. 恢复数据recover](#5. 恢复数据recover)
一.Message模块介绍
由于消息数据需要在网络传输中处理,使用protobuf作为消息类型的定义工具。
protobuf 提供了内置的序列化和反序列化功能,使得消息的编解码操作更加简便。
值得注意的是,消息数据的存储并没有使用传统的数据库方式。由于消息长度的变化幅度较大,部分消息可能非常庞大,数据库的结构化存储并不适合处理这样的动态数据。因此,我们选择将消息直接存储在文件中进行管理。
在内存中,仅需要记录每条消息在文件中的位置以及对应的长度,方便管理。
为了更加高效地管理消息数据,我们以队列为单位进行管理 。
每个队列都拥有自己独立的数据存储文件和内存管理单元。
二.Message的定义
将网络通信的message使用protobuf实现,编写msg.proto,方便后续通信。
1. DeliverMode
枚举
cpp
enum DeliverMode
{
NORMAL = 0;
UNDURABLE = 1;
DURABLE = 2;
}
DeliverMode
枚举定义了消息的三种投递模式:
NORMAL
(值为 0):普通投递模式,表示消息不具有特殊的持久化或非持久化属性。UNDURABLE
(值为 1):非持久投递模式,表示消息在传递过程中不需要持久化,即消息的生命周期较短,不要求在服务器崩溃时保持。DURABLE
(值为 2):持久投递模式,表示消息需要持久化存储,即消息在服务器崩溃或重启时仍需保存并恢复,以确保重要消息不会丢失。
2. BasicAttributes
消息属性
cpp
message BasicAttributes
{
string id = 1;
DeliverMode deliver_mode = 2;
string routine_key = 3;
}
BasicAttributes
消息定义了消息的三个核心属性:
id
:唯一标识符,用于唯一标识每个消息。deliver_mode
:投递模式,使用上面定义的DeliverMode
枚举类型,指示消息的投递方式。routine_key
:路由键,用于消息的路由选择,与消息队列的路由机制相关联
3. Message
消息
Message
消息是消息传递系统中最重要的结构,它包含实际的消息内容,属性及其相关的管理数据。
cpp
message Message
{
message MessageSelf
{
BasicAttributes basic_attributes = 1;
string body = 2;
string valid = 3;
};
MessageSelf msg_self = 1;
uint32 offset = 2;
uint32 length = 3;
}
3.1 MessageSelf
内部消息结构
MessageSelf
是 Message
消息的内部消息结构,仅用于内部(如网络传输过程中)。
它包含三个字段:
basic_attributes
:基本属性,包含消息的唯一标识符、投递模式和路由键。body
:消息体,表示实际的消息内容。valid
:用于验证消息是否合法或有效的字段。
3.2 Message
结构
Message
结构包含了整个消息的封装,它不仅包括 MessageSelf
内部消息结构,还包括额外的控制信息:
msg_self
:内部消息结构MessageSelf
,包含消息的属性、内容和是否合法判断。offset
:偏移量,用于标识消息在传输或存储中的位置。length
:消息长度,表示消息内容的长度或大小,用于传输或存储时的边界确定。
三.MessageMapper的实现
类的主要功能与设计思路
-
消息持久化文件的创建和删除:
- 每个消息队列都有对应的持久化文件(以
.data
为后缀)用于存储消息内容。 - 在构造
MessageMapper
对象时,构造函数会检查并创建保存消息的目录,创建消息持久化文件(通过createFile()
方法)。 removeFile()
方法用于删除消息持久化文件及其临时文件。
- 每个消息队列都有对应的持久化文件(以
-
消息插入与删除:
insert()
方法负责将消息插入到持久化文件中。通过_insert()
子函数来完成实际的写入操作。写入过程中,先写入消息的长度,再写入消息本体。remove()
方法用于标记消息无效,而非真正删除。通过修改消息的valid
字段为"0"
来标记消息为无效,并且通过FileHelper
类直接覆盖文件中的数据。
-
垃圾回收 (GC):
- 在消息持久化过程中,由于消息被标记为无效,文件中可能会存在许多无效数据。为了释放空间,需要进行垃圾回收。
gc()
方法负责将所有有效消息加载到内存中,然后重新写入到一个临时文件(.tmp
后缀),完成后删除原始数据文件并将临时文件重命名为新的数据文件。这一过程可以有效清理文件中的无效消息。
-
恢复消息数据:
load()
方法从持久化文件中加载所有有效消息,消息的读取过程与写入过程相反,先读取消息的长度,再读取消息内容,并判断消息是否有效(通过valid
字段),无效消息将被跳过。
成员变量
- std::string _qname; // 队列名
- std::string _dataFile; // 存储数据的文件名
- std::string _tmpFile; // 临时文件名
代码细节解析
1. 构造函数
cpp
MessageMapper(std::string &dir, const std::string qname)
: _qname(qname)
{
if (dir.back() != '/')
dir.append("/");
if (!FileHelper::createDir(dir))
{
ELOG("create dir %s failed", dir.c_str());
assert(0);
}
_dataFile = dir + _qname + dataSuf;
_tmpFile = dir + _qname + tmpSuf;
createFile();
}
- 构造函数接收保存消息的目录路径
dir
和队列名qname
,构造保存消息的文件路径_dataFile
和临时文件路径_tmpFile
。 - 它会确保目录存在并创建相应的持久化文件。
2. 插入消息
cpp
bool insert(msg_ptr &msg)
{
return _insert(_dataFile, msg);
}
insert()
方法用于将消息插入到持久化文件中。实际的写入操作由 _insert()
方法完成。
cpp
bool _insert(const std::string &name, msg_ptr &msg)
{
std::string msg_str = msg->msg_self().SerializeAsString();
FileHelper helper(name);
size_t offset = helper.size();
size_t start = offset;
size_t len = msg_str.size();
if (!helper.write((char *)&len, offset, sizeof(size_t)))
{
ELOG("%s:write msg length failed", name.c_str());
return false;
}
offset += sizeof(len);
if (!helper.write(msg_str.c_str(), offset, len))
{
ELOG("%s:write msg failed", name.c_str());
return false;
}
offset += len;
msg->set_length(len);
msg->set_offset(start + sizeof(size_t));
return true;
}
- 先将消息序列化为字符串,然后将消息长度和内容依次写入文件。
- 更新消息对象的
length
和offset
,方便管理和后续操作。
3. 删除消息
删除消息的过程是标记消息为无效 ,并覆盖文件中对应的消息内容。
cpp
bool remove(msg_ptr &msg)
{
msg->mutable_msg_self()->set_valid("0");
size_t offset = msg->offset();
std::string str = msg->msg_self().SerializeAsString();
if (str.size() != msg->length())
{
ELOG("msg length is not equal to msg_self length");
return false;
}
FileHelper helper(_dataFile);
if (!helper.write(str.c_str(), offset, str.size()))
{
ELOG("%s:write msg failed", _dataFile.c_str());
return false;
}
return true;
}
4. 垃圾回收gc
cpp
std::list<msg_ptr> gc()
{
std::list<msg_ptr> ret;
if (!load(ret))
{
ELOG("recover failed");
return ret;
}
FileHelper::createFile(_tmpFile);
bool check = true;
for (auto &pmsg : ret)
{
check = _insert(_tmpFile, pmsg);
if (check == false)
{
DLOG("向临时文件写入消息数据失败!!");
return ret;
}
}
check = FileHelper::removeFile(_dataFile);
if (!check)
{
ELOG("删除原数据文件失败!!");
return ret;
}
FileHelper helper(_tmpFile);
check = helper.rename(_dataFile);
if (!check)
{
ELOG("重命名临时文件失败!!");
return ret;
}
return ret;
}
gc()
方法执行垃圾回收操作,恢复所有有效消息,并将它们写入一个新的临时文件,最后替换掉原有的数据文件,并返回一个list,管理垃圾回收后文件中的有效消息。
5. 恢复数据recover
cpp
bool load(std::list<msg_ptr> &ret)
{
FileHelper helper(_dataFile);
size_t offset = 0;
size_t total_size = helper.size();
while (offset < total_size)
{
std::string len_str;
len_str.resize(sizeof(size_t));
if (!helper.read(&len_str[0], offset, sizeof(size_t)))
{
ELOG("%s:read msg length failed", _dataFile.c_str());
return false;
}
size_t len = 0;
std::memcpy(&len, len_str.data(), sizeof(size_t));
std::string msg_str;
msg_str.resize(len);
if (!helper.read(&msg_str[0], offset + sizeof(size_t), len))
{
ELOG("%s:read msg failed", _dataFile.c_str());
return false;
}
offset += sizeof(size_t) + len;
auto msg_ptr = std::make_shared<msg::Message>();
msg_ptr->mutable_msg_self()->ParseFromString(msg_str);
if (msg_ptr->msg_self().valid() == "0")
{
DLOG("msg is invalid");
continue;
}
ret.push_back(msg_ptr);
}
return true;
}
load()
方法从持久化文件中读取所有有效的消息,跳过标记为无效的消息。
四.QueueMessage的实现
QueueMessage
是一个用于管理消息的队列类,主要负责消息的添加、持久化、获取、确认以及垃圾回收 等功能。它通过内存数据结构管理消息的生命周期,并与 MessageMapper
协作,实现消息的持久化存储与恢复。
设计思路
- 消息管理 :通过
_pendings
列表管理待推送消息,_durables
哈希表管理持久化消息,_wait_acks
哈希表管理待确认消息。 - 持久化管理 :使用
MessageMapper
类处理消息的持久化操作,确保了消息即使在系统重启后也能恢复。 - 线程安全 :通过
std::mutex
保护所有对消息队列的操作,确保在多线程下的线程安全。 - 垃圾回收机制 :通过
checkGC()
和Gc()
实现垃圾回收,防止持久化存储空间的冗余。
成员变量
- _qname:队列名称,用于标识消息队列。
- _mutex:互斥锁,确保对队列进行操作时的线程安全。
- _mapper :
MessageMapper
对象,用于管理持久化消息的存储和恢复。 - _pendings :待推送的消息列表,使用
std::list<msg_ptr>
管理。 - _durables :持久化消息的哈希表,使用
std::unordered_map<std::string, msg_ptr>
管理。 - _wait_acks :待确认消息的哈希表,使用
std::unordered_map<std::string, msg_ptr>
管理。 - _valids 和 _total:这两个变量分别记录持久化消息的有效数量和总数量,用于判断是否需要进行垃圾回收。
代码细节解析
构造函数
cpp
QueueMessage(std::string &dir, const std::string qname)
: _qname(qname), _mapper(dir, qname)
{
}
填充队列名以及构造持久化管理句柄_mapper即可。
消息推送 (push
)
功能:将消息添加队列到中,并根据消息的投递模式(DeliverMode)决定是否持久化。
cpp
bool push(const msg::BasicAttributes *pbasic, const std::string &body, bool qmode) // 新增消息
{
// 1.创建消息,并填充
msg_ptr msg = std::make_shared<msg::Message>();
if (pbasic == nullptr)
{
msg::DeliverMode dmode = qmode == true ? msg::DeliverMode::DURABLE : msg::DeliverMode::UNDURABLE;
msg->mutable_msg_self()->mutable_basic_attributes()->set_id(UUIDHelper::uuid());
msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key("default");
}
else
{
msg::DeliverMode dmode = qmode == true ? pbasic->deliver_mode() : msg::DeliverMode::UNDURABLE;
msg->mutable_msg_self()->mutable_basic_attributes()->set_id(pbasic->id());
msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key(pbasic->routine_key());
}
// 2.填充body
msg->mutable_msg_self()->set_body(body);
std::unique_lock<std::mutex> lock(_mutex);
// 3.若需要持久化,则额外添加到持久化对应的_durables和文件中,并继续填充管理字段
if (msg->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
{
msg->mutable_msg_self()->set_valid("1");
bool ret = _mapper.insert(msg); // 先填充好管理字段,再添加到_durables中
if (!ret)
{
DLOG("持久化存储消息:%s 失败了!", body.c_str());
return false;
}
_durables[msg->msg_self().basic_attributes().id()] = msg;
_total++;
_valids++;
}
// 4.添加到_pendings中
_pendings.push_back(msg);
return true;
}
实现步骤:
- 创建消息对象
msg_ptr
,并填充消息的基本属性。 - 根据确定的
qmode
参数或者消息的基本属性决定该消息是否需要持久化。 - 若消息为持久化消息,则将其存储到
MessageMapper
中,并更新_durables
和计数。 - 最后将消息添加到
_pendings
列表,表示该消息等待被推送。
获取消息 (front
)
功能:获取队列中的第一条消息,将其从待推送中移除,添加到待确认队列中。
cpp
msg_ptr front() // 获取队头,方便后续推送给client
{
std::unique_lock<std::mutex> lock(_mutex);
if (_pendings.size() == 0)
{
ILOG("队列%s没有消息了", _qname.c_str());
return msg_ptr();
}
// 1.获取_pendings队首消息
// 2.头删
// 3.添加到_wait_acks中
msg_ptr msg = _pendings.front();
_pendings.pop_front();
_wait_acks.insert(std::make_pair(msg->msg_self().basic_attributes().id(), msg));
return msg;
}
确认消息 (remove
)
功能:从待确认队列中删除消息,并根据情况删除持久化存储中的数据。
cpp
bool remove(const std::string &msg_id) // 确认消息
{
std::unique_lock<std::mutex> lock(_mutex);
// 重复确认,remove
if (_wait_acks.find(msg_id) == _wait_acks.end())
{
DLOG("重复确认消息: msg_id:%s is not in wait_acks", msg_id.c_str());
return true;
}
// 1.若为持久化数据,则先删除持久化数据
if (_wait_acks[msg_id]->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
{
_mapper.remove(_wait_acks[msg_id]);
_durables.erase(msg_id);
_valids--;
// 删除持久化数据之后,才判断是否GC
Gc();
}
// 2.删除_wait_acks对应的数据
_wait_acks.erase(msg_id);
return true;
}
实现步骤:
- 锁定
_mutex
。 - 检查待确认消息队列
_wait_acks
中是否存在该消息。 - 若消息为持久化消息,调用
_mapper.remove
从持久化存储中删除,_durables
并移除该消息。 - 更新有效消息数量
_valids
,并调用Gc
进行垃圾回收(如果需要)。 - 从中
_wait_acks
移除该消息。
清理队列 (clear
)
功能:清空消息队列,删除持久化存储中的数据。
cpp
void clear()
{
std::unique_lock<std::mutex> lock(_mutex);
_mapper.removeFile();
_pendings.clear();
_durables.clear();
_wait_acks.clear();
_valids = _total = 0;
}
实现步骤:
- 锁定
_mutex
。 - 调用
_mapper.removeFile()
删除持久化文件。 - 清空
_pendings
、_durables
和_wait_acks
中的所有消息。
恢复消息 (recover
)
功能:从持久化存储中恢复消息,用于系统重启或异常恢复时重新加载队列消息到内存中
cpp
void recover()
{
std::unique_lock<std::mutex> lock(_mutex);
auto recoveredMessages = _mapper.gc();
if (recoveredMessages.empty())
{
ELOG("队列:%s恢复失败,没有合法的消息返回", _qname.c_str());
return;
}
_pendings = recoveredMessages;
for (auto &ptr : _pendings)
{
_durables.emplace(ptr->msg_self().basic_attributes().id(), ptr);
}
_total = _valids = _durables.size();
}
实现步骤:
- 锁定
_mutex
。 - 调用
_mapper.gc()
恢复所有有效的持久化消息。 - 将恢复的消息重新加入
_pendings
队列,并更新_durables
。
垃圾回收 (Gc
)
功能 :当持久化文件中存在大量无效消息时,进行垃圾回收,删除文件中的无效消息,并更新内存中消息的状态。
cpp
bool checkGC()
{
if (_total > 2000 && _valids * 2 < _total)
return true;
return false;
}
void Gc()
{
if (!checkGC())
return;
// gc后返回所有合法的消息,更新内存中的数据
auto new_list = _mapper.gc();
for (auto ptr : new_list)
{
std::string msg_id = ptr->msg_self().basic_attributes().id();
if (_durables.find(msg_id) == _durables.end())
{
DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
// 持久化的消息没有在内存中被管理,则重新加入_pendings
_pendings.push_back(ptr);
_durables[msg_id] = ptr;
continue;
}
// 更新_durables
_durables[msg_id] = ptr;
// 更新_total
_total = _valids = _durables.size();
}
}
实现步骤:
- 调用
checkGC()
判断是否满足GC条件(如持久化消息总数超过2000且有效消息低于总数的一半)。 - 如果需要垃圾返回,则调用
_mapper.gc()
方法重新加载有效消息,并更新_pendings
和_durables
。
五.MessageManager的实现
MessageManager
类旨在管理多个消息队列,通过提供队列初始化、消息插入、确认、获取等功能,帮助我们处理消息的持久化存储与队列操作。
主要思路是利用C++的std::unordered_map
存储多个消息队列(QueueMessage
),并通过互斥锁(std::mutex
)来保证多线程环境下对这些队列的安全访问。此外,通过共享指针(std::shared_ptr
)来管理消息队列对象的生命周期,避免手动管理内存带来的复杂性和潜在的内存泄漏问题,其中的接口是对QueueMessage的封装。
cpp
class QueueMessage
{
using msg_ptr = std::shared_ptr<msg::Message>;
private:
std::string _qname; // 对应的队列名称
std::mutex _mutex; // 一个队列对应一把锁
MessageMapper _mapper; // 持久化的管理句柄
// 内存中管理消息的数据结构
std::list<msg_ptr> _pendings; // 待推送消息
std::unordered_map<std::string, msg_ptr> _durables; // 持久化消息
std::unordered_map<std::string, msg_ptr> _wait_acks; // 待确认消息
// 持久化文件中的消息数量
size_t _valids = 0; // 有效消息数量(针对持久化的消息,用来checkGc)
size_t _total = 0; // 总消息数量(针对持久化的消息,用来checkGc,文件中的msg数量)
public:
QueueMessage(std::string &dir, const std::string qname)
: _qname(qname), _mapper(dir, qname)
{
}
bool push(const msg::BasicAttributes *pbasic, const std::string &body, bool qmode) // 新增消息
{
// 1.创建消息,并填充
msg_ptr msg = std::make_shared<msg::Message>();
if (pbasic == nullptr)
{
msg::DeliverMode dmode = qmode == true ? msg::DeliverMode::DURABLE : msg::DeliverMode::UNDURABLE;
msg->mutable_msg_self()->mutable_basic_attributes()->set_id(UUIDHelper::uuid());
msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key("default");
}
else
{
msg::DeliverMode dmode = qmode == true ? pbasic->deliver_mode() : msg::DeliverMode::UNDURABLE;
msg->mutable_msg_self()->mutable_basic_attributes()->set_id(pbasic->id());
msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key(pbasic->routine_key());
}
// 2.填充body
msg->mutable_msg_self()->set_body(body);
std::unique_lock<std::mutex> lock(_mutex);
// 3.若需要持久化,则额外添加到持久化对应的_durables和文件中,并继续填充管理字段
if (msg->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
{
// 先填充好管理字段,再添加到_durables中
msg->mutable_msg_self()->set_valid("1");
bool ret = _mapper.insert(msg);
if (!ret)
{
DLOG("持久化存储消息:%s 失败了!", body.c_str());
return false;
}
_durables[msg->msg_self().basic_attributes().id()] = msg;
_total++;
_valids++;
}
// 4.添加到_pendings中
_pendings.push_back(msg);
return true;
}
msg_ptr front() // 获取队头,方便后续推送给client
{
std::unique_lock<std::mutex> lock(_mutex);
if (_pendings.size() == 0)
{
ILOG("队列%s没有消息了", _qname.c_str());
return msg_ptr();
}
// 1.获取_pendings队首消息
// 2.头删
// 3.添加到_wait_acks中
msg_ptr msg = _pendings.front();
_pendings.pop_front();
_wait_acks.insert(std::make_pair(msg->msg_self().basic_attributes().id(), msg));
return msg;
}
bool remove(const std::string &msg_id) // 确认消息
{
std::unique_lock<std::mutex> lock(_mutex);
// 重复确认,remove
if (_wait_acks.find(msg_id) == _wait_acks.end())
{
DLOG("重复确认消息: msg_id:%s is not in wait_acks", msg_id.c_str());
return true;
}
// 1.若为持久化数据,则先删除持久化数据
if (_wait_acks[msg_id]->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
{
_mapper.remove(_wait_acks[msg_id]);
_durables.erase(msg_id);
_valids--;
// 删除持久化数据之后,才判断是否GC
Gc();
}
// 2.删除_wait_acks对应的数据
_wait_acks.erase(msg_id);
return true;
}
void clear()
{
std::unique_lock<std::mutex> lock(_mutex);
_mapper.removeFile();
_pendings.clear();
_durables.clear();
_wait_acks.clear();
_valids = _total = 0;
}
size_t validNum()
{
std::unique_lock<std::mutex> lock(_mutex);
return _valids;
}
size_t totalNum()
{
std::unique_lock<std::mutex> lock(_mutex);
return _total;
}
size_t waitAckNum()
{
std::unique_lock<std::mutex> lock(_mutex);
return _wait_acks.size();
}
size_t pendingNum()
{
std::unique_lock<std::mutex> lock(_mutex);
return _pendings.size();
}
void recover()
{
std::unique_lock<std::mutex> lock(_mutex);
auto recoveredMessages = _mapper.gc();
if (recoveredMessages.empty())
{
ELOG("队列:%s恢复失败,没有合法的消息返回", _qname.c_str());
return;
}
_pendings = recoveredMessages;
for (auto &ptr : _pendings)
{
//_durables.insert(std::make_pair(ptr->msg_self().basic_attributes().id(), ptr));
_durables.emplace(ptr->msg_self().basic_attributes().id(), ptr);
// ILOG("recover id: %s",ptr->msg_self().basic_attributes().id().c_str());
// ILOG("第%d条消息",ptr->msg_self().msg_id())")
}
_total = _valids = _durables.size();
}
private:
bool checkGC()
{
if (_total > 2000 && _valids * 2 < _total)
return true;
return false;
}
void Gc()
{
if (!checkGC())
return;
auto new_list = _mapper.gc();
if (new_list.empty())
{
ELOG("垃圾回收后没有合法的消息返回");
return;
}
// gc后返回所有合法的消息,再去更新内存中的数据
for (auto ptr : new_list)
{
std::string msg_id = ptr->msg_self().basic_attributes().id();
if (_durables.find(msg_id) == _durables.end()) // 一般不会出现这种情况
{
DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
// 持久化的消息没有在内存中被管理,则重新加入_pendings
_pendings.push_back(ptr);
_durables[msg_id] = ptr;
continue;
}
// 更新_durables,实际更新的是 存储位置和长度
_durables[msg_id]->set_offset(ptr->offset());
_durables[msg_id]->set_length(ptr->length());
}
// 更新_total和_valid
_total = _valids = _durables.size();
// std::cout << "Gc后_total:%d, _valids:%d, _durables.size:%d";
}
};
class MessageManager
{
using ptr = std::shared_ptr<QueueMessage>;
using msg_ptr = std::shared_ptr<msg::Message>;
public:
using mmp = std::shared_ptr<MessageManager>;
private:
std::mutex _mutex;
std::string _dir;
std::unordered_map<std::string, ptr> _msgs;
public:
MessageManager(const std::string &dir)
: _dir(dir) {}
void clear()
{
std::unique_lock<std::mutex> lock(_mutex);
for (auto &kv : _msgs)
{
kv.second->clear();
}
_msgs.clear();
}
void initQueueMsg(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it != _msgs.end())
{
ILOG("队列%s已经存在,无需init", qname.c_str());
return;
}
_msgs.insert(std::make_pair(qname, std::make_shared<QueueMessage>(_dir, qname)));
tmp = _msgs[qname];
}
_msgs[qname]->recover(); // 恢复持久化数据,用QueueMessage自己的mutex即可
}
void destroyQueueMsg(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,destroyQueueMsg fail", qname.c_str());
return;
}
tmp = it->second;
_msgs.erase(it);
}
tmp->clear();
}
bool insertMsg(const std::string &qname, msg::BasicAttributes *bp, const std::string &body, bool mode)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法insertMsg", qname.c_str());
return false;
}
tmp = it->second;
}
return tmp->push(bp, body, mode);
}
void ack(const std::string &qname, const std::string &id)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法ack", qname.c_str());
return;
}
tmp = it->second;
}
tmp->remove(id);
}
msg_ptr front(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取队首元素", qname.c_str());
return msg_ptr();
}
tmp = it->second;
}
return tmp->front();
}
size_t validNum(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取validNum", qname.c_str());
return -1;
}
tmp = it->second;
}
// 减少锁冲突,MessageManager的临界区越小越好
return tmp->validNum();
}
size_t totalNum(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取totalNum", qname.c_str());
return -1;
}
tmp = it->second;
}
return tmp->totalNum();
}
size_t waitAckNum(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取waitAckNum", qname.c_str());
return -1;
}
tmp = it->second;
}
return tmp->waitAckNum();
}
size_t pendingNum(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取pendingNum", qname.c_str());
return -1;
}
tmp = it->second;
}
return tmp->pendingNum();
}
};
六.message.hpp全部代码
cpp
#pragma once
#include "../common_mq/helper.hpp"
#include "../common_mq/logger.hpp"
#include "../common_mq/msg.pb.h"
#include <string>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <cassert>
#include <cstring>
#include <list>
// 消息持久化映射类,用文件而不是数据库
namespace mq
{
class MessageMapper
{
using msg_ptr = std::shared_ptr<msg::Message>;
const std::string dataSuf = ".data";
const std::string tmpSuf = ".tmp";
private:
std::string _qname; // 队列名
std::string _dataFile; // 存储数据的文件名
std::string _tmpFile; // 临时文件名
public:
MessageMapper(std::string &dir, const std::string qname)
: _qname(qname)
{
if (dir.back() != '/')
dir.append("/");
// 创建目录
if (!FileHelper::createDir(dir))
{
ELOG("create dir %s failed", dir.c_str());
assert(0);
}
_dataFile = dir + _qname + dataSuf;
_tmpFile = dir + _qname + tmpSuf;
createFile();
}
// 1.创建/删除msg数据文件
bool createFile()
{
return FileHelper::createFile(_dataFile);
}
void removeFile()
{
FileHelper::removeFile(_dataFile);
FileHelper::removeFile(_tmpFile);
}
// 2.插入/删除msg数据
bool insert(msg_ptr &msg)
{
return _insert(_dataFile, msg);
}
bool remove(msg_ptr &msg)
{
// 常规情况下,修改msg和文件中的valid为"0"即可
// 必要时进行gc垃圾回收
// 1.修改msg中的
msg->mutable_msg_self()->set_valid("0");
// 2.覆盖式修改文件中的内容
size_t offset = msg->offset();
std::string str = msg->msg_self().SerializeAsString();
if (str.size() != msg->length())
{
ELOG("msg length is not equal to msg_self length");
return false;
}
FileHelper helper(_dataFile);
if (!helper.write(str.c_str(), offset, str.size()))
{
ELOG("%s:write msg failed", _dataFile.c_str());
return false;
}
return true;
}
// 3.recover和gc相关
std::list<msg_ptr> gc()
{
std::list<msg_ptr> ret;
// 1. 从文件中恢复所有有效数据
if (!load(ret))
{
ELOG("recover failed");
return ret;
}
// 2. 打开tmp文件,遍历有效数据,创建并写入tmp文件中
FileHelper::createFile(_tmpFile);
bool check = true;
for (auto &pmsg : ret)
{
check = _insert(_tmpFile, pmsg);
if (check == false)
{
DLOG("向临时文件写入消息数据失败!!");
return ret;
}
}
// 3. 删除原来的data文件
check = FileHelper::removeFile(_dataFile);
if (!check)
{
ELOG("删除原数据文件失败!!");
return ret;
}
// 4. 重命名tmp文件为data文件
FileHelper helper(_tmpFile);
check = helper.rename(_dataFile);
if (!check)
{
ELOG("重命名临时文件失败!!");
return ret;
}
return ret;
}
bool load(std::list<msg_ptr> &ret) // 将文件中的内容加载到内存的list中
{
FileHelper helper(_dataFile);
size_t offset = 0;
size_t total_size = helper.size();
// ILOG("total_size: %ld", total_size);
while (offset < total_size)
{
// 1. 读取size_t字节长度
std::string len_str;
len_str.resize(sizeof(size_t));
if (!helper.read(&len_str[0], offset, sizeof(size_t)))
{
ELOG("%s:read msg length failed", _dataFile.c_str());
return false;
}
// 2. 读取msg_self内容
size_t len = 0;
std::memcpy(&len, len_str.data(), sizeof(size_t));
ILOG("len: %zu", len);
std::string msg_str;
msg_str.resize(len);
if (!helper.read(&msg_str[0], offset + sizeof(size_t), len))
{
ELOG("%s:read msg failed", _dataFile.c_str());
return false;
}
offset += sizeof(size_t) + len;
auto msg_ptr = std::make_shared<msg::Message>();
msg_ptr->mutable_msg_self()->ParseFromString(msg_str);
// 文件中的无效信息不会恢复
if (msg_ptr->msg_self().valid() == "0") // 无效消息
{
DLOG("msg is invalid");
continue;
}
ret.push_back(msg_ptr);
}
return true;
}
// 子函数
private:
bool _insert(const std::string &name, msg_ptr &msg)
{
// a.将msg内部的MessageSelf序列化
// b.将字符串写入文件末尾,先写入size_t字节长度,再写string内容
// c.填充引用的msg的相关管理字段,使其在内存中可以管理文件内容
std::string msg_str = msg->msg_self().SerializeAsString();
FileHelper helper(name);
size_t offset = helper.size();
size_t start = offset;
size_t len = msg_str.size();
// 先写入size_t字节长度
if (!helper.write((char *)&len, offset, sizeof(size_t)))
{
ELOG("%s:write msg length failed", name.c_str());
return false;
}
offset += sizeof(len);
// 再写入序列化后的字符串msg_str
if (!helper.write(msg_str.c_str(), offset, len))
{
ELOG("%s:write msg failed", name.c_str());
return false;
}
offset += len;
msg->set_length(len);
msg->set_offset(start + sizeof(size_t));
return true;
}
};
class QueueMessage
{
using msg_ptr = std::shared_ptr<msg::Message>;
private:
std::string _qname; // 对应的队列名称
MessageMapper _mapper; // 持久化的管理句柄
// 内存中管理消息的数据结构
std::list<msg_ptr> _pendings; // 待推送消息
std::unordered_map<std::string, msg_ptr> _durables; // 持久化消息
std::unordered_map<std::string, msg_ptr> _wait_acks; // 待确认消息
// 持久化文件中的消息数量
size_t _valids = 0; // 有效消息数量(针对持久化的消息,用来checkGc)
size_t _total = 0; // 总消息数量(针对持久化的消息,用来checkGc,文件中的msg数量)
std::mutex _mutex; // 一个队列对应一把锁
public:
public:
QueueMessage(std::string &dir, const std::string qname)
: _qname(qname), _mapper(dir, qname)
{
}
bool push(const msg::BasicAttributes *pbasic, const std::string &body, bool qmode) // 新增消息
{
// 1.创建消息,并填充
msg_ptr msg = std::make_shared<msg::Message>();
if (pbasic == nullptr)
{
msg::DeliverMode dmode = qmode == true ? msg::DeliverMode::DURABLE : msg::DeliverMode::UNDURABLE;
msg->mutable_msg_self()->mutable_basic_attributes()->set_id(UUIDHelper::uuid());
msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key("default");
}
else
{
msg::DeliverMode dmode = qmode == true ? pbasic->deliver_mode() : msg::DeliverMode::UNDURABLE;
msg->mutable_msg_self()->mutable_basic_attributes()->set_id(pbasic->id());
msg->mutable_msg_self()->mutable_basic_attributes()->set_deliver_mode(dmode);
msg->mutable_msg_self()->mutable_basic_attributes()->set_routine_key(pbasic->routine_key());
}
// 2.填充body
msg->mutable_msg_self()->set_body(body);
std::unique_lock<std::mutex> lock(_mutex);
// 3.若需要持久化,则额外添加到持久化对应的_durables和文件中,并继续填充管理字段
if (msg->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
{
msg->mutable_msg_self()->set_valid("1");
bool ret = _mapper.insert(msg); // 先填充好管理字段,再添加到_durables中
if (!ret)
{
DLOG("持久化存储消息:%s 失败了!", body.c_str());
return false;
}
_durables[msg->msg_self().basic_attributes().id()] = msg;
_total++;
_valids++;
}
// 4.添加到_pendings中
_pendings.push_back(msg);
return true;
}
msg_ptr front() // 获取队头,方便后续推送给client
{
std::unique_lock<std::mutex> lock(_mutex);
if (_pendings.size() == 0)
{
ILOG("队列%s没有消息了", _qname.c_str());
return msg_ptr();
}
// 1.获取_pendings队首消息
// 2.头删
// 3.添加到_wait_acks中
msg_ptr msg = _pendings.front();
_pendings.pop_front();
_wait_acks.insert(std::make_pair(msg->msg_self().basic_attributes().id(), msg));
return msg;
}
bool remove(const std::string &msg_id) // 确认消息
{
std::unique_lock<std::mutex> lock(_mutex);
// 重复确认,remove
if (_wait_acks.find(msg_id) == _wait_acks.end())
{
DLOG("重复确认消息: msg_id:%s is not in wait_acks", msg_id.c_str());
return true;
}
// 1.若为持久化数据,则先删除持久化数据
if (_wait_acks[msg_id]->msg_self().basic_attributes().deliver_mode() == msg::DeliverMode::DURABLE)
{
_mapper.remove(_wait_acks[msg_id]);
_durables.erase(msg_id);
_valids--;
// 删除持久化数据之后,才判断是否GC
Gc();
}
// 2.删除_wait_acks对应的数据
_wait_acks.erase(msg_id);
return true;
}
void clear()
{
std::unique_lock<std::mutex> lock(_mutex);
_mapper.removeFile();
_pendings.clear();
_durables.clear();
_wait_acks.clear();
_valids = _total = 0;
}
size_t validNum()
{
std::unique_lock<std::mutex> lock(_mutex);
return _valids;
}
size_t totalNum()
{
std::unique_lock<std::mutex> lock(_mutex);
return _total;
}
size_t waitAckNum()
{
std::unique_lock<std::mutex> lock(_mutex);
return _wait_acks.size();
}
size_t pendingNum()
{
std::unique_lock<std::mutex> lock(_mutex);
return _pendings.size();
}
void recover()
{
std::unique_lock<std::mutex> lock(_mutex);
_pendings = _mapper.gc();
for (auto &ptr : _pendings)
{
_durables.insert(std::make_pair(ptr->msg_self().basic_attributes().id(), ptr));
// ILOG("recover id: %s",ptr->msg_self().basic_attributes().id().c_str());
// ILOG("第%d条消息",ptr->msg_self().msg_id())")
}
_total = _valids = _durables.size();
}
private:
bool checkGC()
{
if (_total > 2000 && _valids * 2 < _total)
return true;
return false;
}
void Gc()
{
if (!checkGC())
return;
// gc后返回所有合法的消息,更新内存中的数据
auto new_list = _mapper.gc();
for (auto ptr : new_list)
{
std::string msg_id = ptr->msg_self().basic_attributes().id();
if (_durables.find(msg_id) == _durables.end())
{
DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
// 持久化的消息没有在内存中被管理,则重新加入_pendings
_pendings.push_back(ptr);
_durables[msg_id] = ptr;
continue;
}
// 更新_durables
_durables[msg_id] = ptr;
// 更新_total
_total = _valids = _durables.size();
// std::cout << "Gc后_total:%d, _valids:%d, _durables.size:%d";
}
}
};
class MessageManager
{
using ptr = std::shared_ptr<QueueMessage>;
using msg_ptr = std::shared_ptr<msg::Message>;
public:
using mmp = std::shared_ptr<MessageManager>;
private:
std::mutex _mutex;
std::string _dir;
std::unordered_map<std::string, ptr> _msgs;
public:
MessageManager(const std::string &dir)
: _dir(dir) {}
void clear()
{
std::unique_lock<std::mutex> lock(_mutex);
for (auto &kv : _msgs)
{
kv.second->clear();
}
_msgs.clear();
}
void initQueueMsg(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it != _msgs.end())
{
ILOG("队列%s已经存在,无需init", qname.c_str());
return;
}
_msgs.insert(std::make_pair(qname, std::make_shared<QueueMessage>(_dir, qname)));
tmp = _msgs[qname];
}
_msgs[qname]->recover(); // 恢复持久化数据,用QueueMessage自己的mutex即可
}
void destroyQueueMsg(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,destroyQueueMsg fail", qname.c_str());
return;
}
tmp = it->second;
_msgs.erase(it);
}
tmp->clear();
}
bool insertMsg(const std::string &qname, msg::BasicAttributes *bp, const std::string &body, bool mode)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法insertMsg", qname.c_str());
return false;
}
tmp = it->second;
}
return tmp->push(bp, body, mode);
}
void ack(const std::string &qname, const std::string &id)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法ack", qname.c_str());
return;
}
tmp = it->second;
}
tmp->remove(id);
}
msg_ptr front(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取队首元素", qname.c_str());
return msg_ptr();
}
tmp = it->second;
}
return tmp->front();
}
size_t validNum(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取validNum", qname.c_str());
return -1;
}
tmp = it->second;
}
// 减少锁冲突,MessageManager的临界区越小越好
return tmp->validNum();
}
size_t totalNum(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取totalNum", qname.c_str());
return -1;
}
tmp = it->second;
}
return tmp->totalNum();
}
size_t waitAckNum(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取waitAckNum", qname.c_str());
return -1;
}
tmp = it->second;
}
return tmp->waitAckNum();
}
size_t pendingNum(const std::string &qname)
{
ptr tmp;
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _msgs.find(qname);
if (it == _msgs.end())
{
ELOG("队列%s不存在,无法获取pendingNum", qname.c_str());
return -1;
}
tmp = it->second;
}
return tmp->pendingNum();
}
};
};