【RabbitMQ 项目】服务端:数据管理模块之消息管理

文章目录

一.编写思路

1.定义消息类

因为消息要在网络中传输,并且还要持久化到磁盘,所以使用 Protobuf 来定义,目的就是使用它的序列化反序列化方法。

首先需要客户端填充传递过来的字段:

  1. 消息 id:在服务端上用来唯一标识一条消息
  2. 路由关键字 routing_key:和路由交换有关
  3. 投递模式 delivery_mode:分为持久化和非持久化两种,非持久化表示这条消息不太重要,丢了也就丢了
  4. 消息主体 body
    再加上一个字段:
  5. 消息有效标志位 is_valid:服务端持久化时要用到的。磁盘上的消息是顺序存储的,想要删除一条消息,直接删除需要把大量消息往前移动覆盖,成本太高。解决方案就是将消息的有效标志位置为无效,具体来说先把消息提取上来反序列化,修改有效标志位,然后覆盖式写回
    以上四个字段是需要存储到磁盘上的,下面还有 3 个和持久化相关字段,要在内存中记录
  6. 磁盘上消息偏移量 offset:你要去将把消息置为无效,首先要在文件中找到那条消息
  7. 磁盘上消息的长度 length:找到消息起点后,还要把它提取上来
    补充说明:

Protobuf 中 bool 类型 true 和 false 序列化后长度不一样,如果消息有效标志位使用 bool 类型,修改该字段然后序列化得到的结果长短不一,写回磁盘会覆盖其它消息,所以这个字段要用 string,"0"表示无效,"1"表示有效,都占 1 个字节

2.定义消息持久化类

前置说明:

  1. 为什么不用数据库而用磁盘?

    1. 有的消息体积很大,不适合用 sqlite 这样的小型数据库存储
    2. 消息持久化和确认应答删除是很频繁的动作,比交换机,队列,绑定这些要频繁地多,RabbitMQ 作为一个成熟的中间件,要追求效率,所以自己存储方案,更加灵活且高效
  2. 1 个文件还是多个文件?
    每个队列中的消息最好分开持久化,因为持久化相关的操作不仅涉及到文件,还有内存,比如移动消息的位置,内存中的 offset 字段就会修改,这个时候如果所有消息都放在一起管理,文件锁,互斥锁竞争非常激烈,所以最好的办法以队列为单位管理消息,每个队列的消息有自己独立的文件。

  3. 文件存储方案?
    消息中有偏移量和长度字段,可以根据内存中的消息找到磁盘消息,但是程序刚启动时,内存是没有消息的,而我们的目的是把消息恢复到内存。那我们怎么保证读取到一个个完整消息呢?所以需要定制存储协议。定义如下:
    先存储 8 字节消息长度,然后再存储消息。这和网络协议是类似的,4 字节消息长度就是报头,消息就是有效载荷,怎么把报头和有效载荷分离?定长 4 字节报头,怎么读取一个完整报文?4 字节 + 消息长度即为一个完整的报文
    成员变量:

  4. 队列名称

  5. 存储队列消息的文件路径

  6. 转移队列消息的临时文件路径
    成员方法:

  7. 构造函数:确保数据文件存在

  8. 插入消息:根据传入的消息智能指针,将消息需要持久化的部分写入到文件末尾,并且修改内存中的 offset 和 length 字段

  9. 删除消息:根据传入的消息智能指针,在磁盘上找到消息并反序列化,把有效标志位置"0",重新写回原位

  10. 恢复消息:顺序地读取消息文件,把有效的消息插入链表中,返回给外界

  11. 垃圾回收:由于删除消息只是修改了数据内容,并没有删除数据,所以消息不断累积给磁盘带来巨大压力,所以我们要定期清理掉无效的消息。怎么清理呢?先把磁盘上有效消息都加载到内存,处理完后把删除原来存储消息的文件,对临时文件重命名。最后把链表返回给外界
    补充说明:

为什么恢复消息要返回链表?

它的上层,队列消息管理类会在程序启动时调用它,从文件中恢复出来的消息都会插入到待推送链表中,所以 recover 接口返回的链表可以直接作为待推送的消息链表

3.定义队列消息管理类

成员变量:

  1. 队列名称
  2. 队列消息持久化句柄
  3. 持久化消息的总数量
  4. 持久化消息中有效消息的总数量
    这两个字段是为了判断是否需要垃圾回收,当文件中消息总数大于 2000,且有效消息比例不足 50% 时就垃圾回收
  5. 待消费的消息(链表)
    选择链表是因为有大量的队头和队尾操作,并且链表比队列更加灵活,可以访问中间元素(本项目没有用到)
  6. 待应答的消息(哈希表,key:message id, value:MessagePtr)
    客户端拿着消息 id 来确认的,所有要用 id 作为键值
  7. 持久化的消息(哈希表,key:message id, value:MessagePtr)
    其一,当要在文件中删除一条持久化的消息,需要找到它的偏移量以及长度,从哪找呢?就拿着消息 id,到这个成员中去找。为什么要用 id 做键值,因为一般是消息确认之后,就要删除持久化消息,而消息确认用的就是 id。
    其二,垃圾回收后,需要更新内存中消息的 offset 字段,在哪找到消息呢?就从这里找
    成员方法:
  8. 构造函数:恢复文件中的消息到待消费的消息链表(后续改进)
  9. 插入消息:传入的参数告诉我是否需要持久化,先持久化,并在持久化消息列表中添加该消息,再尾插到待消费的消息链表中
  10. 移除消息:传入的是消息 id,根据 id 再在待应答消息列表中找到消息,如果已经持久化就先删除持久化消息,然后在在持久化消息列表中移除该消息,最后再 erase 待应答列表中的消息
  11. 取队首消息:从待消费链表中头部取出一条消息,再把它插入待应答列表,最后再返回给外部
    重要的私有成员方法:
  12. 垃圾回收:先要判断是否需要垃圾回收,如果需要再调用队列消息持久化句柄的垃圾回收方法,得到一个新链表,用每个结点的 offset 字段去更新持久化列表中的每个消息。每次移除持久化的消息后,就要检查是否需要垃圾回收

4.定义消息管理类

成员变量:

  1. 队列消息管理句柄(哈希表:key:队列名称,value:队列消息管理句柄指针)
    成员方法:
  2. 构造函数
    传入所有队列名称,消息文件所在目录,去构造队列消息管理句柄(在它们构造函数中恢复消息)
  3. 向指定队列插入消息
  4. 向指定队列应答消息
    调用队里消息管理句柄的移除消息方法
  5. 移除指定队列的所有消息
    当客户端把队列删除时会调用此接口

二.代码实践

Message.hpp:

cpp 复制代码
#pragma once
#include "../common/message.pb.h"
#include "../common/Util.hpp"
#include <memory>
#include <list>
#include <mutex>
namespace ns_data
{
    class Message;
    class QueueMessageManager;

    using MessagePtr = std::shared_ptr<Message>;
    using QueueMessageManagerPtr = std::shared_ptr<QueueMessageManager>;

    static const std::string dataFileSuffix = ".data";
    static const std::string tmpFileSuffix = ".data.tmp";
    class QueueMessageMapper
    {
    private:
        std::string _qname;
        std::string _dataFilePath;
        std::string _tmpFilePath;

    public:
        QueueMessageMapper(std::string baseDir, const std::string &qname)
            : _qname(qname)
        {
            if (baseDir.back() != '/')
            {
                baseDir += '/';
            }
            _dataFilePath = baseDir + _qname + dataFileSuffix;
            _tmpFilePath = baseDir + _qname + tmpFileSuffix;

            // 确保数据文件存在
            if (!ns_util::FileUtil::createFile(_dataFilePath))
            {
                LOG(FATAL) << "create message file fail, file: " << _dataFilePath << endl;
                exit(1);
            }
        }

        /***************
         * 插入消息
         * 注意:*messagePtr是一个输入输出型参数
         * ******************/
        bool insertMessage(MessagePtr msgPtr)
        {
            return insertMessageIntoFile(msgPtr, _dataFilePath);
        }

        /***************
         * 移除消息
         * 注意:*messagePtr是一个输入输出型参数,但这不重要,因为messagePtr在外界一定会从链表中移除
         * ******************/
        bool removeMessage(MessagePtr msgPtr)
        {
            size_t pos = msgPtr->offset();
            size_t msgLen = msgPtr->length();

            ns_util::FileUtil fileUtil(_dataFilePath);
            if (!fileUtil.isOpen())
            {
                LOG(WARNING) << "open message file " << _dataFilePath << " fail" << endl;
                return false;
            }

            // 1.读取消息
            std::string msgBytes;
            if (!fileUtil.read(&msgBytes, pos, msgLen))
            {
                LOG(WARNING) << "read msg fail" << endl;
                return false;
            }

            // 2.反序列化
            if (!msgPtr->mutable_saved_info()->ParseFromString(msgBytes)) // 注意!!!
            {
                LOG(WARNING) << "parse Message fail, offset: " << pos << ", msgLen: " << msgLen << ", msgBytes: "
                             << msgBytes << endl;
                return false;
            }

            // 3.修改标志位为"0"
            msgPtr->mutable_saved_info()->set_valid("0");

            // 4.序列化
            std::string newMsgBytes = msgPtr->saved_info().SerializeAsString(); // 注意
            if (newMsgBytes.size() != msgLen)
            {
                cout << "修改有效标志位后序列化长度与之前不一致, old: " << msgLen << ", new: " << newMsgBytes.size() << endl;
                return false;
            }

            // 5.写回原处
            if (!fileUtil.write(newMsgBytes.c_str(), pos, msgLen))
            {
                LOG(WARNING) << "rewrite msg fail" << endl;
                return false;
            }
            return true;
        }

        /*************
         * 程序启动时调用,只能成功,失败直接退出
         * listPtr是输出型参数
         * ******************/
        void recoverMessages(std::list<MessagePtr> *listPtr)
        {

            ns_util::FileUtil fileUtil(_dataFilePath);
            if (!fileUtil.isOpen())
            {
                LOG(WARNING) << "open message file " << _dataFilePath << " fail" << endl;
                exit(1);
            }

            size_t pos = 0;
            size_t fsize = fileUtil.size();
            while (pos < fsize)
            {
                size_t msgLen;
                // 先读取8字节长度
                if (!fileUtil.read(&msgLen, pos, sizeof(msgLen)))
                {
                    LOG(WARNING) << "read 8 byte msgLen fail, pos=" << pos << ", fsize=" << fsize << endl;
                    exit(1);
                }
                // LOG(INFO) << "read msgLen success, msgLen: " << msgLen << endl;

                pos += sizeof(msgLen);
                // 再读取消息
                std::string msgBytes;
                if (!fileUtil.read(&msgBytes, pos, msgLen))
                {
                    LOG(WARNING) << "read msg fail" << endl;
                    exit(1);
                }

                // LOG(INFO) << "read msg success, msgBytes: " << msgBytes << endl;

                // 反序列化
                MessagePtr msgPtr = std::make_shared<Message>();
                if (!msgPtr->mutable_saved_info()->ParseFromString(msgBytes))
                {
                    LOG(WARNING) << "parse Message fail" << endl;
                    exit(1);
                }

                if (msgPtr->saved_info().valid() == "1")
                {
                    msgPtr->set_length(msgBytes.size());
                    msgPtr->set_offset(pos);
                    listPtr->push_back(msgPtr);
                }

                pos += msgLen;
            }
        }

        /***************
         * 垃圾回收
         * *******************/
        bool gc(std::list<MessagePtr> *listPtr)
        {
            recoverMessages(listPtr);

            // 确保临时文件存在,且是一个崭新的
            ns_util::FileUtil::removeFileOrDir(_tmpFilePath);
            if (!ns_util::FileUtil::createFile(_tmpFilePath))
            {
                LOG(FATAL) << "create message file fail, file: " << _tmpFilePath << endl;
                return false;
            }
            for (auto it = listPtr->begin(); it != listPtr->end(); it++)
            {
                if (!insertMessageIntoFile(*it, _tmpFilePath))
                {
                    return false;
                }
            }

            // 删除数据文件,给临时文件重命名
            assert(ns_util::FileUtil::removeFileOrDir(_dataFilePath));
            assert(ns_util::FileUtil::renameFile(_tmpFilePath, _dataFilePath));
            return true;
        }

        /***************
         * 删除消息文件
         * ****************/
        void removeDataFile()
        {
            assert(ns_util::FileUtil::removeFileOrDir(_dataFilePath));
        }

    private:
        bool insertMessageIntoFile(MessagePtr msgPtr, const std::string &pathName)
        {
            // 1.有效标志位设为"1"
            msgPtr->mutable_saved_info()->set_valid("1");
            // 2.对需要存储的部分序列化
            std::string msg = msgPtr->saved_info().SerializeAsString();

            ns_util::FileUtil fileUtil(pathName);
            if (!fileUtil.isOpen())
            {
                LOG(WARNING) << "open message file " << pathName << " fail" << endl;
                return false;
            }

            // 3.先写入8字节长度
            size_t msgSize = msg.size();
            size_t fsize = fileUtil.size();
            if (!fileUtil.write(&msgSize, fsize, sizeof(msgSize)))
            {
                LOG(WARNING) << "write 8 Byte msgSize fail" << endl;
                return false;
            }

            // 4.写入消息
            if (!fileUtil.write(msg))
            {
                LOG(WARNING) << "write msg fail" << endl;
                return false;
            }

            // 5.填充offset和length字段
            msgPtr->set_offset(fsize + sizeof(msgSize));
            msgPtr->set_length(msgSize);

            return true;
        }
    };

    class QueueMessageManager
    {
    private:
        std::string _qname;
        QueueMessageMapper _mapper;
        size_t _validDurableNum;
        size_t _totalDurableNum;
        std::list<MessagePtr> _waitConsumeMessages;
        std::unordered_map<std::string, MessagePtr> _waitAckMessages;
        std::unordered_map<std::string, MessagePtr> _durableMessages;
        std::mutex _mtx;

    public:
        QueueMessageManager(const std::string &qname, const std::string &baseDir)
            : _qname(qname),
              _mapper(baseDir, _qname),
              _validDurableNum(0),
              _totalDurableNum(0)
        {
            // 恢复队列消息
            _mapper.recoverMessages(&_waitConsumeMessages);
            for (const auto &msgPtr : _waitConsumeMessages)
            {
                _durableMessages[msgPtr->saved_info().id()] = msgPtr;
            }
            _validDurableNum = _totalDurableNum = _durableMessages.size();
        }

        /***************
         * 向待消费队列尾插一条消息,根据需要看是否要持久化
         * 我们允许一条消息没有id,如果没有id,还需要持久化,我们就给它手动设置一个uuid
         * **************/
        bool insertMessage(const std::string &id, const std::string &routingKey, const std::string &body,
                           DeliveryMode deliveryMode)
        {
            auto msgPtr = std::make_shared<Message>();
            if (id == "")
            {
                msgPtr->mutable_saved_info()->set_id(ns_util::UUIDUtil::uuid());
            }
            else
            {
                msgPtr->mutable_saved_info()->set_id(id);
            }
            msgPtr->mutable_saved_info()->set_routing_key(routingKey);
            msgPtr->mutable_saved_info()->set_delivery_mode(deliveryMode);
            msgPtr->mutable_saved_info()->set_body(body);

            std::unique_lock<std::mutex> lck(_mtx);
            // 判断是否要持久化
            if (deliveryMode == DeliveryMode::DURABLE)
            {
                if (!_mapper.insertMessage(msgPtr))
                {
                    LOG(WARNING) << "持久化消息失败, message body: " << msgPtr->saved_info().body() << endl;
                    return false;
                }
                _durableMessages[msgPtr->saved_info().id()] = msgPtr;
                _totalDurableNum++;
                _validDurableNum++;
            }
            _waitConsumeMessages.push_back(msgPtr);
            return true;
        }

        /**************
         * 移除一条待确认应答消息
         * **************/
        bool removeMessage(const std::string &id)
        {
            std::unique_lock<std::mutex> lck(_mtx);
            if (_waitAckMessages.count(id) == 0)
            {
                LOG(INFO) << "message not found in waitAckMessages, id: " << id << endl;
                return true;
            }
            auto msgPtr = _waitAckMessages[id];
            // 看看它是否持久化了
            if (msgPtr->saved_info().delivery_mode() == DeliveryMode::DURABLE)
            {
                if (!_mapper.removeMessage(msgPtr))
                {
                    LOG(WARNING) << "remove durable message in file fail, message body: "
                                 << msgPtr->saved_info().body() << endl;
                    return false;
                }
                _validDurableNum--;
                _durableMessages.erase(id);
            }
            _waitAckMessages.erase(id);

            // 最后检查是否需要垃圾回收
            if (gcCheck())
            {
                gc();
            }
            return true;
        }

        /**************
         * 取走队头消息
         * ***************/
        MessagePtr popFront()
        {
            std::unique_lock<std::mutex> lck(_mtx);
            if (_waitConsumeMessages.empty())
            {
                return nullptr;
            }
            auto msgPtr = _waitConsumeMessages.front();
            _waitConsumeMessages.pop_front();
            _waitAckMessages[msgPtr->saved_info().id()] = msgPtr;
            return msgPtr;
        }

        /***************
         * 清空队列消息
         * **************/
        void clear()
        {
            std::unique_lock<std::mutex> lck(_mtx);
            _mapper.removeDataFile();
            _waitConsumeMessages.clear();
            _waitAckMessages.clear();
            _durableMessages.clear();
            _validDurableNum = _totalDurableNum = 0;
        }

        /************
         * 以下成员仅作调试用
         * ***************/
        size_t waitConsumeSize()
        {
            std::unique_lock<std::mutex> lck(_mtx);
            return _waitConsumeMessages.size();
        }

        size_t waitAckSize()
        {
            std::unique_lock<std::mutex> lck(_mtx);
            return _waitAckMessages.size();
        }
        size_t durableSize()
        {
            std::unique_lock<std::mutex> lck(_mtx);
            return _durableMessages.size();
        }

    private:
        bool gcCheck()
        {
            if (_totalDurableNum >= 2000 && _validDurableNum * 10 / _totalDurableNum < 5)
            {
                return true;
            }
            return false;
        }

        bool gc()
        {
            std::list<MessagePtr> newMessageList;
            if (!_mapper.gc(&newMessageList))
            {
                LOG(WARNING) << "gc fail" << endl;
                return false;
            }

            for (const auto &msgPtr : newMessageList)
            {
                std::string id = msgPtr->saved_info().id();
                if (_durableMessages.count(id) == 0)
                {
                    LOG(WARNING) << "one durable message not be managered, message body: "
                                 << msgPtr->saved_info().body() << endl;
                    // 补救措施:添加到待消费链表和持久化列表中
                    _waitConsumeMessages.push_back(msgPtr);
                    _durableMessages[id] = msgPtr;
                }
                else
                {
                    // 更新offset
                    _durableMessages[id]->set_offset(msgPtr->offset());
                    assert(_durableMessages[id]->length() == msgPtr->length());
                }
            }

            _validDurableNum = _totalDurableNum = _durableMessages.size();
            return true;
        }
    };

    class MessageManager
    {
    private:
        std::unordered_map<std::string, QueueMessageManagerPtr> _qMsgManagers;
        std::mutex _mtx;

    public:
        MessageManager(const std::string &baseDir, const std::vector<std::string> &qnames)
        {
            // 构造函数无需锁住
            for (const auto &qname : qnames)
            {
                _qMsgManagers[qname] = std::make_shared<QueueMessageManager>(qname, baseDir); // 完成后消息已经恢复了
            }
        }

        /***************
         * 向指定队列插入消息
         * ***************/
        bool insertMessage(const std::string &qname, const std::string &id, const std::string &routingKey,
                           const std::string &body, DeliveryMode deliveryMode)
        {
            std::unique_lock<std::mutex> lck(_mtx);

            if (_qMsgManagers.count(qname) == 0)
            {
                LOG(WARNING) << "insert message fail, because MessageQueue not found, qname: " << qname << endl;
                return false;
            }
            return _qMsgManagers[qname]->insertMessage(id, routingKey, body, deliveryMode);
        }

        /************
         * 对指定队列的消息应答
         * *************/
        void ack(const std::string &qname, const std::string &msgId)
        {
            std::unique_lock<std::mutex> lck(_mtx);

            if (_qMsgManagers.count(qname) == 0)
            {
                LOG(INFO) << "ack error, because MessageQueue not found, qname: " << qname << endl;
                return;
            }
            _qMsgManagers[qname]->removeMessage(msgId);
        }

        /***********
         * 移除指定队列的消息,当删除一个队列时会用到
         * ***********/
        void removeQueueMessages(const std::string &qname)
        {
            std::unique_lock<std::mutex> lck(_mtx);

            if (_qMsgManagers.count(qname) == 0)
            {
                LOG(WARNING) << "removeQueueMessages fail, because MessageQueue not found, qname: " << qname << endl;
                return;
            }
            _qMsgManagers[qname]->clear();
            _qMsgManagers.erase(qname);
        }

        /*********
         * 取出指定队列队首消息
         * **************/
        MessagePtr popFront(const std::string &qname)
        {
            std::unique_lock<std::mutex> lck(_mtx);

            if (_qMsgManagers.count(qname) == 0)
            {
                LOG(WARNING) << "popFront fail, because MessageQueue not found, qname: " << qname << endl;
                return nullptr;
            }
            return _qMsgManagers[qname]->popFront();
        }

        /***********
         * 清除所有消息(仅调试)
         * *****************/
        void clear()
        {
            std::unique_lock<std::mutex> lck(_mtx);
            for (auto it = _qMsgManagers.begin(); it != _qMsgManagers.end(); it++)
            {
                it->second->clear();
            }
        }

        /**********
         * 以下成员仅作调试用
         * ***************/
        size_t waitConsumeSize(const std::string &qname)
        {
            std::unique_lock<std::mutex> lck(_mtx);
            if (_qMsgManagers.count(qname) == 0)
            {
                LOG(WARNING) << qname << " not found" << qname << endl;
                return 0;
            }
            return _qMsgManagers[qname]->waitConsumeSize();
        }

        size_t waitAckSize(const std::string &qname)
        {
            std::unique_lock<std::mutex> lck(_mtx);
            if (_qMsgManagers.count(qname) == 0)
            {
                LOG(WARNING) << qname << " not found" << qname << endl;
                return 0;
            }
            return _qMsgManagers[qname]->waitAckSize();
        }
        size_t durableSize(const std::string &qname)
        {
            std::unique_lock<std::mutex> lck(_mtx);
            if (_qMsgManagers.count(qname) == 0)
            {
                LOG(WARNING) << qname << " not found" << qname << endl;
                return 0;
            }
            return _qMsgManagers[qname]->durableSize();
        }
    };
}

Util.hpp:

cpp 复制代码
#pragma once
#include "Log.hpp"
#include <string>
#include <sqlite3.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <fstream>
#include <sstream>
#include <random>
#include <iomanip>
#include <atomic>
using namespace ns_log;
namespace ns_util
{
    class Sqlite3Util
    {
    private:
        std::string _dbfile;
        sqlite3 *_handler;
        bool _isOpen;

    public:
        Sqlite3Util(const std::string &dbfile)
            : _dbfile(dbfile),
              _handler(nullptr),
              _isOpen(false)
        {
            open();
        }

        ~Sqlite3Util()
        {
            close();
        }

        bool open(int safeLevel = SQLITE_OPEN_FULLMUTEX)
        {
            if (_isOpen)
            {
                return true;
            }

            // 可读可写,不存在就创建,默认串行化访问
            int ret = sqlite3_open_v2(_dbfile.c_str(), &_handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safeLevel,
                                      nullptr);
            if (ret != SQLITE_OK)
            {
                LOG(WARNING) << sqlite3_errmsg(_handler) << endl;
                return false;
            }

            _isOpen = true;
            return true;
        }
        // int sqlite3_exec(sqlite3*, char *sql, int (*callback) (void* arg,int colNum ,char** lines,char** fields), void* arg, char **err)
        bool exec(const std::string &sql, int (*cb)(void *, int, char **, char **), void *arg)
        {
            if (sqlite3_exec(_handler, sql.c_str(), cb, arg, nullptr) != SQLITE_OK)
            {
                LOG(WARNING) << "execute fail: error: " << sqlite3_errmsg(_handler) << endl;
                return false;
            }
            return true;
        }
        void close()
        {
            if (_handler)
            {
                sqlite3_close_v2(_handler);
            }
        }
    };

    class FileUtil
    {
    private:
        std::string _pathName;
        std::fstream _fs;

    public:
        FileUtil(const std::string &pathName)
            : _pathName(pathName),
              _fs(_pathName, std::ios::in | std::ios::out | std::ios::binary)
        {
            if (!_fs.is_open())
            {
                LOG(WARNING) << "open file fail: " << _pathName << endl;
            }
        }

        bool isOpen()
        {
            return _fs.is_open();
        }
        /***************
         * 从指定位置开始写指定个字节的内容
         * *****************/
        bool write(const void *buf, size_t pos, size_t len)
        {
            _fs.seekp(pos);
            _fs.write((char *)buf, len);
            if (!_fs.good())
            {
                LOG(WARNING) << "write fail, error: " << strerror(errno) << endl;
                return false;
            }
            return true;
        }

        /******************
         * 向文件末尾写string
         * ***************/
        bool write(const std::string &content)
        {
            return write(content.c_str(), size(), content.size());
        }

        /**************
         * 从指定位置开始读取指定字节的内容
         * ************/
        bool read(void* buf, size_t pos, size_t len)
        {
            _fs.seekg(pos);
            _fs.read((char *)buf, len);
            if (!_fs.good())
            {
                LOG(WARNING) << "read fail, error: " << strerror(errno) << endl;
                return false;
            }
            return true;
        }

        bool read(std::string *contentPtr, size_t pos, size_t len)
        {
            contentPtr->resize(len, '\0');
            return read((void*)(contentPtr->c_str()), pos, len);
        }

        /********************
         * 读取整个文件
         * *****************/
        bool read(std::string *contentPtr)
        {
            size_t fsize = size();
            contentPtr->resize(fsize, '\0');
            return read((void *)contentPtr->c_str(), 0, fsize);
        }

        size_t size()
        {
            size_t curPos = _fs.tellg();
            _fs.seekg(0, std::ios::end);
            size_t ret = _fs.tellg();

            // 恢复原来的位置
            _fs.seekg(curPos);
            return ret;
        }
        static void getParentDirectory(const std::string &pathName, std::string *dirPtr)
        {
            // 从后往前找,找到第一个"/"
            auto pos = pathName.rfind('/');
            if (pos == std::string::npos)
            {
                *dirPtr = "./";
                return;
            }
            *dirPtr = pathName.substr(0, pos);
        }

        static bool createDirectory(const std::string &dirName)
        {
            // 从第一个父目录开始,逐层创建
            size_t prev = 0;
            while (true)
            {
                auto pos = dirName.find('/', prev);
                if (pos == std::string::npos)
                {
                    break;
                }

                std::string dir = dirName.substr(0, pos);
                int ret = mkdir(dir.c_str(), 0775);
                if (ret != 0 && errno != EEXIST)
                {
                    LOG(WARNING) << "创建目录" << dir << "失败, error: " << strerror(errno) << endl;
                    return false;
                }

                prev = pos + 1;
            }

            int ret = mkdir(dirName.c_str(), 0775);
            if (ret != 0 && errno != EEXIST)
            {
                LOG(WARNING) << "创建目录" << dirName << "失败, error: " << strerror(errno) << endl;
                return false;
            }

            return true;
        }

        static bool createFile(const std::string pathName)
        {
            // 先创建它的父目录
            std::string parentDir;
            getParentDirectory(pathName, &parentDir);
            if (!createDirectory(parentDir))
            {
                LOG(WARNING) << "创建文件失败,因为创建父目录失败" << endl;
                return false;
            }
            // 再创建文件
            int fd = open(pathName.c_str(), O_CREAT, 0775);
            if (fd == -1)
            {
                LOG(WARNING) << "创建文件失败, error: " << strerror(errno) << endl;
                return false;
            }
            close(fd);
            return true;
        }

        static bool removeFileOrDir(const std::string &name)
        {

            if (remove(name.c_str()) != 0)
            {
                LOG(WARNING) << "remove " << name << " fail, error: " << strerror(errno) << endl;
                return false;
            }
            return true;
        }

        static bool renameFile(const std::string &oldPathName, const std::string &newPathName)
        {
            if (rename(oldPathName.c_str(), newPathName.c_str()) == -1)
            {
                LOG(WARNING) << "rename " << oldPathName << " to " << newPathName << " fail, error: "
                             << strerror(errno) << endl;
                return false;
            }
            return true;
        }
    };

    class UUIDUtil
    {
    public:
        static std::string uuid()
        {
            std::random_device rd;
            std::mt19937_64 gernator(rd());
            std::uniform_int_distribution<int> distribution(0, 255);
            std::stringstream ss;
            for (int i = 0; i < 8; i++)
            {
                ss << std::setw(2) << std::setfill('0')
                   << std::hex << distribution(gernator);
                if (i == 3 || i == 5 || i == 7)
                {
                    ss << "-";
                }
            }
            static std::atomic<size_t> seq(1);
            size_t num = seq.fetch_add(1);
            for (int i = 7; i >= 0; i--)
            {
                ss << std::setw(2) << std::setfill('0') << std::hex << ((num >> (i * 8)) & 0xff);
                if (i == 6)
                    ss << "-";
            }
            return ss.str();
        }
    };

}
相关推荐
全栈开发圈6 分钟前
干货分享|分布式数据科学工具 Xorbits 的使用
分布式
运维&陈同学2 小时前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列
时差9532 小时前
Flink Standalone集群模式安装部署
大数据·分布式·flink·部署
菠萝咕噜肉i2 小时前
超详细:Redis分布式锁
数据库·redis·分布式·缓存·分布式锁
只因在人海中多看了你一眼6 小时前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
zhixingheyi_tian8 小时前
Spark 之 Aggregate
大数据·分布式·spark
求积分不加C10 小时前
-bash: ./kafka-topics.sh: No such file or directory--解决方案
分布式·kafka
nathan052910 小时前
javaer快速上手kafka
分布式·kafka
谭震鸿13 小时前
Zookeeper集群搭建Centos环境下
分布式·zookeeper·centos
天冬忘忧18 小时前
Kafka 工作流程解析:从 Broker 工作原理、节点的服役、退役、副本的生成到数据存储与读写优化
大数据·分布式·kafka