仿RabbitMQ实现消息队列服务端(一)

文章目录

整体框架:工具模块及项目整体模块框架

交换机数据管理

交换机数据管理就是描述了交换机应该有哪些数据

  1. 定义交换机数据类

1、交换机的名称:也是交换机的唯一标识

2、交换机的类型:决定了消息的转发方式(三种 )

每个队列与交换机绑定信息中有binding_key,每条消息中有routing_key

复制代码
    1.直接交换:binding_key与routing_key相同时,将消息放入队列

    2.广播交换:交换机绑定的所有队列都放入消息

    3.主题交换:根据具体的匹配算法,将符合匹配条件的binding_key和routing_key对应的队列放入消息。

3、持久化标志:决定当前交换机的数据是否需要持久化存储

4、自动删除标志:如果关联该交换机的客户端都退出了,是否需要自动删除交换机。

5、交换机的其他参数,在本项目中未具体使用。

cpp 复制代码
//1. 定义交换机类
    struct Exchange {
        using ptr = std::shared_ptr<Exchange>;
        //1. 交换机名称
        std::string name;
        //2. 交换机类型
        ExchangeType type;
        //3. 交换机持久化标志
        bool durable;
        //4. 是否自动删除标志
        bool auto_delete;
        //5. 其他参数
        google::protobuf::Map<std::string, std::string> args;

        Exchange() {}
        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) {}
        //args存储键值对,在存储数据库的时候,会组织一个格式字符串进行存储 key=val&key=val....
        //内部解析str_args字符串,将内容存储到成员中
        void setArgs(const std::string &str_args) {
            //key=val&key=val&
            std::vector<std::string> sub_args;
            StrHelper::split(str_args, "&", sub_args);
            for (auto &str : sub_args) {
                size_t pos = str.find("=");
                std::string key = str.substr(0, pos);
                std::string val = str.substr(pos + 1);
                args[key] = val;
            }
        }
        //将args中的内容进行序列化后,返回一个字符串
        std::string getArgs() {
            std::string result;
            for (auto start = args.begin(); start != args.end(); ++start) {
                result += start->first + "=" + start->second + "&";
            }
            return result;
        }
    };

定义交换机数据持久化类(数据持久化的sqlite3数据库中)

  • 创建/删除交换机数据表
  • 新增交换机数据
  • 移除交换机数据
  • 查询所有交换机数据
  • 查询指定交换机数据(根据名称)
cpp 复制代码
using ExchangeMap = std::unordered_map<std::string, Exchange::ptr>;
    //2. 定义交换机数据持久化管理类--数据存储在sqlite数据库中
    class ExchangeMapper {
        public:
            ExchangeMapper(const std::string &dbfile):_sql_helper(dbfile) {
                std::string path = FileHelper::parentDirectory(dbfile);
                FileHelper::createDirectory(path);
                assert(_sql_helper.open());
                createTable();
            }
            void createTable() {
                #define CREATE_TABLE "create table if not exists exchange_table(\
                    name varchar(32) primary key, \
                    type int, \
                    durable int, \
                    auto_delete int, \
                    args varchar(128));"
                bool ret = _sql_helper.exec(CREATE_TABLE, nullptr, nullptr);
                if (ret == false) {
                    DLOG("创建交换机数据库表失败!!");
                    abort();//直接异常退出程序
                }
            }
            void removeTable() {
                #define DROP_TABLE "drop table if exists exchange_table;"
                bool ret = _sql_helper.exec(DROP_TABLE, nullptr, nullptr);
                if (ret == false) {
                    DLOG("删除交换机数据库表失败!!");
                    abort();//直接异常退出程序
                }
            }
            bool insert(Exchange::ptr &exp) {
                std::stringstream ss;
                ss << "insert into exchange_table values(";
                ss << "'" << exp->name << "', ";
                ss << exp->type << ", ";
                ss << exp->durable << ", ";
                ss << exp->auto_delete << ", ";
                ss << "'" << exp->getArgs() << "');";
                return _sql_helper.exec(ss.str(), nullptr, nullptr);
            }
            void remove(const std::string &name) {
                std::stringstream ss;
                ss << "delete from exchange_table where name=";
                ss << "'" << name << "';";
                _sql_helper.exec(ss.str(), nullptr, nullptr);
            }
            ExchangeMap recovery() {
                ExchangeMap result;
                std::string sql = "select name, type, durable, auto_delete, args from exchange_table;";
                _sql_helper.exec(sql, selectCallback, &result);
                return result;
            }
        private:
            static int selectCallback(void* arg,int numcol,char** row,char** fields) {
                ExchangeMap *result = (ExchangeMap*)arg;
                auto exp = std::make_shared<Exchange>();
                exp->name = row[0];
                exp->type = (nzq::ExchangeType)std::stoi(row[1]);
                exp->durable = (bool)std::stoi(row[2]);
                exp->auto_delete = (bool)std::stoi(row[3]);
                if (row[4]) exp->setArgs(row[4]);
                result->insert(std::make_pair(exp->name, exp));
                return 0;
            }
        private:
            SqliteHelper _sql_helper;
    };
  1. 定义交换机数据管理类
  • 声明交换机,并添加管理(存在则OK,不存在则创建)
  • 删除交换机
  • 获取指定交换机
  • 销毁所有交换机数据
cpp 复制代码
    //3. 定义交换机数据内存管理类 
    class ExchangeManager {
        public:
            using ptr = std::shared_ptr<ExchangeManager>;
            ExchangeManager(const std::string &dbfile) : _mapper(dbfile){
                _exchanges = _mapper.recovery();
            }
            //声明交换机
            bool declareExchange(const std::string &name,
                ExchangeType type, bool durable, bool auto_delete,
                const google::protobuf::Map<std::string, std::string> &args) {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _exchanges.find(name);
                if (it != _exchanges.end()) {
                    //如果交换机已经存在,那就直接返回,不需要重复新增。
                    return true;
                }
                auto exp = std::make_shared<Exchange>(name, type, durable, auto_delete, args);
                if (durable == true) {
                    bool ret = _mapper.insert(exp);
                    if (ret == false) return false;
                }
                _exchanges.insert(std::make_pair(name, exp));
                return true;
            }
            //删除交换机
            void deleteExchange(const std::string &name) {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _exchanges.find(name);
                if (it == _exchanges.end()) {
                    return;
                }
                if(it->second->durable == true) _mapper.remove(name);
                _exchanges.erase(name);
            }
            //获取指定交换机对象
            Exchange::ptr selectExchange(const std::string &name) {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _exchanges.find(name);
                if (it == _exchanges.end()) {
                    return Exchange::ptr();
                }
                return it->second;
            }
            //判断交换机是否存在
            bool exists(const std::string &name) {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _exchanges.find(name);
                if (it == _exchanges.end()) {
                    return false;
                }
                return true;
            }
            size_t size() {
                std::unique_lock<std::mutex> lock(_mutex);
                return _exchanges.size();
            }
            //清理所有交换机数据
            void clear() {
                std::unique_lock<std::mutex> lock(_mutex);
                _mapper.removeTable();
                _exchanges.clear();
            }
        private:
            std::mutex _mutex;
            ExchangeMapper _mapper;
            ExchangeMap _exchanges;
    };

队列数据管理

当前队列数据的管理,本质上是队列描述信息的管理,描述当前服务器上有哪些队列。

  1. 定义队列描述数据类
  • 队列名称
  • 是否持久化标志
  • 是否独占标志(并不使用
  • 是否⾃动删除标志(并不使用
  • 其他参数(并不使用
cpp 复制代码
struct MsgQueue {
        using ptr = std::shared_ptr<MsgQueue>;
        std::string name;
        bool durable;
        bool exclusive;
        bool auto_delete;
        google::protobuf::Map<std::string, std::string> args;

        MsgQueue(){}
        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){}
            
        void setArgs(const std::string &str_args) {
            std::vector<std::string> sub_args;
            StrHelper::split(str_args, "&", sub_args);
            for (auto &str : sub_args) {
                size_t pos = str.find("=");
                std::string key = str.substr(0, pos);
                std::string val = str.substr(pos + 1);
                args[key] = val;
            }
        }
        std::string getArgs() {
            std::string result;
            for (auto start = args.begin(); start != args.end(); ++start) {
                result += start->first + "=" + start->second + "&";
            }
            return result;
        }
    };
  1. 定义队列数据持久化类(数据持久化的sqlite3数据库中)
  • 创建/删除队列数据表
  • 新增队列数据
  • 移除队列数据
  • 查询所有队列数据
cpp 复制代码
using QueueMap = std::unordered_map<std::string, MsgQueue::ptr>;
    class MsgQueueMapper {
        public:
            MsgQueueMapper(const std::string &dbfile):_sql_helper(dbfile) {
                std::string path = FileHelper::parentDirectory(dbfile);
                FileHelper::createDirectory(path);
                _sql_helper.open();
                createTable();
            }
            void createTable() {
                std::stringstream sql;
                sql << "create table if not exists queue_table(";
                sql << "name varchar(32) primary key, ";
                sql << "durable int, ";
                sql << "exclusive int, ";
                sql << "auto_delete int, ";
                sql << "args varchar(128));";
                assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
            }
            void removeTable() {
                std::string sql = "drop table if exists queue_table;";
                _sql_helper.exec(sql, nullptr, nullptr);
            }
            bool insert(MsgQueue::ptr &queue) {
                // insert into queue_table values('queue1', true, false, false, "k1=v1&k2=v2&");
                std::stringstream sql;
                sql << "insert into queue_table values(";
                sql << "'" << queue->name << "', ";
                sql << queue->durable << ", ";
                sql << queue->exclusive << ", ";
                sql << queue->auto_delete << ", ";
                sql << "'" << queue->getArgs() << "');";
                return _sql_helper.exec(sql.str(), nullptr, nullptr);
            }
            void remove(const std::string &name) {
                // delete from queue_table where name='queue1';
                std::stringstream sql;
                sql << "delete from queue_table where name=";
                sql << "'" << name << "';";
                _sql_helper.exec(sql.str(), nullptr, nullptr);
            }
            QueueMap recovery() {
                QueueMap result;
                std::string sql = "select name, durable, exclusive, auto_delete, args from queue_table;";
                _sql_helper.exec(sql, selectCallback, &result);
                return result;
            }
        private:
            static int selectCallback(void* arg,int numcol,char** row,char** fields) {
                QueueMap *result = (QueueMap*)arg;
                MsgQueue::ptr mqp = std::make_shared<MsgQueue>();
                mqp->name = row[0];
                mqp->durable = (bool)std::stoi(row[1]);
                mqp->exclusive = (bool)std::stoi(row[2]);
                mqp->auto_delete = (bool)std::stoi(row[3]);
                if (row[4]) mqp->setArgs(row[4]);
                result->insert(std::make_pair(mqp->name, mqp));
                return 0;
            }
        private:
            SqliteHelper _sql_helper;
    };
  1. 定义队列数据管理类
  • 创建队列,并添加管理(存在则OK,不存在则创建)
  • 删除队列
  • 获取指定队列
  • 获取所有队列
  • 判断指定队列是否存在
  • 获取队列数量
  • 销毁所有队列数据
cpp 复制代码
    class MsgQueueManager {
        public:
            using ptr = std::shared_ptr<MsgQueueManager>;
            MsgQueueManager(const std::string &dbfile):_mapper(dbfile) {
                _msg_queues = _mapper.recovery();
            }
            bool declareQueue(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> lock(_mutex);
                auto it = _msg_queues.find(qname);
                if (it != _msg_queues.end()) {
                    return true;
                }
                MsgQueue::ptr mqp = std::make_shared<MsgQueue>();
                mqp->name = qname;
                mqp->durable = qdurable;
                mqp->exclusive = qexclusive;
                mqp->auto_delete = qauto_delete;
                mqp->args = qargs;
                if (qdurable == true) {
                    bool ret = _mapper.insert(mqp);
                    if (ret == false) return false;
                }
                _msg_queues.insert(std::make_pair(qname, mqp));
                return true;
            }
            void deleteQueue(const std::string &name) {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msg_queues.find(name);
                if (it == _msg_queues.end()) {
                    return ;
                }
                if (it->second->durable == true) _mapper.remove(name);
                _msg_queues.erase(name);
            }
            MsgQueue::ptr selectQueue(const std::string &name) {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msg_queues.find(name);
                if (it == _msg_queues.end()) {
                    return MsgQueue::ptr();
                }
                return it->second;
            }
            QueueMap allQueues() {
                std::unique_lock<std::mutex> lock(_mutex);
                return _msg_queues;
            }
            bool exists(const std::string &name) {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _msg_queues.find(name);
                if (it == _msg_queues.end()) {
                    return false;
                }
                return true;
            }
            size_t size() {
                std::unique_lock<std::mutex> lock(_mutex);
                return _msg_queues.size();
            }
            void clear() {
                _mapper.removeTable();
                _msg_queues.clear();
            }
        private:
            std::mutex _mutex;
            MsgQueueMapper _mapper;
            QueueMap _msg_queues;
    };

绑定信息(交换机-队列)管理

绑定信息,本质上就是⼀个交换机关联了哪些队列的描述。

  1. 定义绑定信息类
  • 交换机名称
  • 队列名称
  • binding_key(分发匹配规则-决定了哪些数据能被交换机放⼊队列)
cpp 复制代码
struct Binding
    {
        using ptr = std::shared_ptr<Binding>;
        std::string exchange_name;
        std::string msgqueue_name;
        std::string binding_key; // 分发匹配规则-决定了哪些数据能被交换机放⼊队列

        Binding() {}
        Binding(const std::string &ename, const std::string &qname, const std::string &key) 
        	: exchange_name(ename), msgqueue_name(qname), binding_key(key) {}
    };
  1. 定义绑定信息数据持久化类(数据持久化的sqlite3数据库中)
  • 创建/删除绑定信息数据表
  • 新增绑定信息数据
  • 移除指定绑定信息数据
  • 移除指定交换机相关绑定信息数据:移除交换机的时候会被调⽤
  • 移除指定队列相关绑定信息数据:移除队列的时候会被调⽤
  • 查询所有绑定信息数据:⽤于重启服务器时进⾏历史数据恢复

一个交换机可能会绑定多个队列,所以我们用了两个映射:

  • 第一个队列名映射binding
  • 第二个交换机名映射第一个
cpp 复制代码
    // 队列与绑定信息是一一对应的(因为是给某个交换机去绑定队列,因此一个交换机可能会有多个队列的绑定信息)
    // 因此先定义一个队列名,与绑定信息的映射关系,这个是为了方便通过队列名查找绑定信息
    using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
    // 然后定义一个交换机名称与队列绑定信息的映射关系,这个map中包含了所有的绑定信息,并且以交换机为单元进行了区分
    using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;

    // std::unordered_map<std::string, Binding::ptr>;  队列与绑定  ,
    // std::unordered_map<std::string, Binding::ptr>;  交换机与绑定
    // 采用上边两个结构,则删除交换机相关绑定信息的时候,不仅要删除交换机映射,还要删除对应队列中的映射,否则对象得不到释放

    class BindingMapper
    {
    public:
        BindingMapper(const std::string &dbfile) : _sql_helper(dbfile)
        {
            std::string path = FileHelper::parentDirectory(dbfile);
            FileHelper::createDirectory(path);
            _sql_helper.open();
            createTable();
        }
        void createTable()
        {
            // create table if not exists binding_table(exchange_name varchar(32), msgqueue_name, binding_key)
            std::stringstream sql;
            sql << "create table if not exists binding_table(";
            sql << "exchange_name varchar(32), ";
            sql << "msgqueue_name varchar(32), ";
            sql << "binding_key varchar(128));";
            assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
        }
        void removeTable()
        {
            std::string sql = "drop table if exists binding_table;";
            _sql_helper.exec(sql, nullptr, nullptr);
        }
        bool insert(Binding::ptr &binding)
        {
            // insert into binding_table values('exchange1', 'msgqueue1', 'news.music.#');
            std::stringstream sql;
            sql << "insert into binding_table values(";
            sql << "'" << binding->exchange_name << "', ";
            sql << "'" << binding->msgqueue_name << "', ";
            sql << "'" << binding->binding_key << "');";
            return _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void remove(const std::string &ename, const std::string &qname)
        {
            // delete from binding_table where exchange_name='' and msgqueue_name='';
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "exchange_name='" << ename << "' and ";
            sql << "msgqueue_name='" << qname << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void removeExchangeBindings(const std::string &ename)
        {
            // delete from binding_table where exchange_name='';
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "exchange_name='" << ename << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void removeMsgQueueBindings(const std::string &qname)
        {
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "msgqueue_name='" << qname << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        BindingMap recovery()
        {
            BindingMap result;
            // select exchange_name, msgqueue_name, binding_key from binding_table;
            std::string sql = "select exchange_name, msgqueue_name, binding_key from binding_table;";
            _sql_helper.exec(sql, selectCallback, &result);
            return result;
        }

    private:
        static int selectCallback(void *arg, int numcol, char **row, char **fields)
        {
            BindingMap *result = (BindingMap *)arg;
            Binding::ptr bp = std::make_shared<Binding>(row[0], row[1], row[2]);
            // 为了防止 交换机相关的绑定信息已经存在,因此不能直接创建队列映射,进行添加,这样会覆盖历史数据
            // 因此得先获取交换机对应的映射对象,往里边添加数据
            // 但是,若这时候没有交换机对应的映射信息,因此这里的获取要使用引用(会保证不存在则自动创建)
            MsgQueueBindingMap &qmap = (*result)[bp->exchange_name];
            qmap.insert(std::make_pair(bp->msgqueue_name, bp));
            return 0;
        }

    private:
        SqliteHelper _sql_helper;
    };
  1. 定义绑定信息数据管理类
  • 创建绑定信息,并添加管理(存在则OK,不存在则创建)
  • 解除指定的绑定信息
  • 删除指定队列的所有绑定信息
  • 删除交换机相关的所有绑定信息
  • 获取交换机相关的所有绑定信息:交换机收到消息后,需要分发给⾃⼰关联的队列
  • 判断指定绑定信息是否存在
  • 获取当前绑定信息数量
  • 销毁所有绑定信息数据
cpp 复制代码
class BindingManager
    {
    public:
        using ptr = std::shared_ptr<BindingManager>;
        BindingManager(const std::string &dbfile) : _mapper(dbfile)
        {
            _bindings = _mapper.recovery();
        }

        bool bind(const std::string &ename, const std::string &qname, const std::string &key, bool durable)
        {
            // 加锁,构造一个队列的绑定信息对象, 添加映射关系
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _bindings.find(ename);
            if (it != _bindings.end() && it->second.find(qname) != it->second.end())
            {
                return true;
            }
            // 绑定信息是否需要持久化,取决于什么? 交换机数据是持久化的,以及队列数据也是持久化的。
            Binding::ptr bp = std::make_shared<Binding>(ename, qname, key);
            if (durable)
            {
                bool ret = _mapper.insert(bp);
                if (ret == false)
                    return false;
            }
            auto &qbmap = _bindings[ename];
            qbmap.insert(std::make_pair(qname, bp));
            return true;
        }

        void unBind(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if (eit == _bindings.end())
            {
                return;
            } // 没有交换机相关的绑定信息
            auto qit = eit->second.find(qname);
            if (qit == eit->second.end())
            {
                return;
            } // 交换机没有队列相关的绑定信息
            _mapper.remove(ename, qname);
            _bindings[ename].erase(qname);
        }
        void removeExchangeBindings(const std::string &ename)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeExchangeBindings(ename);
            _bindings.erase(ename);
        }
        void removeMsgQueueBindings(const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeMsgQueueBindings(qname);
            for (auto start = _bindings.begin(); start != _bindings.end(); ++start)
            {
                // 遍历每个交换机的绑定信息,从中移除指定队列的相关信息
                start->second.erase(qname);
            }
        }
        MsgQueueBindingMap getExchangeBindings(const std::string &ename)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if (eit == _bindings.end())
            {
                return MsgQueueBindingMap();
            }
            return eit->second;
        }

        Binding::ptr getBinding(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if (eit == _bindings.end())
            {
                return Binding::ptr();
            }
            auto qit = eit->second.find(qname);
            if (qit == eit->second.end())
            {
                return Binding::ptr();
            }
            return qit->second;
        }
        bool exists(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if (eit == _bindings.end())
            {
                return false;
            }
            auto qit = eit->second.find(qname);
            if (qit == eit->second.end())
            {
                return false;
            }
            return true;
        }
        size_t size()
        {
            size_t total_size = 0;
            std::unique_lock<std::mutex> lock(_mutex);
            for (auto start = _bindings.begin(); start != _bindings.end(); ++start)
            {
                // 遍历每个交换机的绑定信息,从中移除指定队列的相关信息
                total_size += start->second.size();
            }
            return total_size;
        }
        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeTable();
            _bindings.clear();
        }

    private:
        std::mutex _mutex;
        BindingMapper _mapper;
        BindingMap _bindings;
    };

队列消息管理

因为消息数据需要在⽹络中进⾏传输,因此消息的类型定义使⽤protobuf进⾏,因为protobuf中⾃带了序列化和反序列化功能,因此操作起来会简便⼀些。

需要特别说明的是,消息的存储并没有使⽤数据库,因为消息⻓度通常不定,且有些消息可能会⾮常庞⼤,因此并不适合存储在数据库中,因此我们的处理⽅式(包括RabbitMQ)是直接将消息存储在⽂件中进⾏管理,⽽内存中管理的消息只需要记录好⾃⼰在⽂件中的所在位置和⻓度即可。为了便于管理,消息管理以队列为单元进⾏管理,因此每个队列都会有⾃⼰独⽴的数据存储⽂件。

  1. 创建消息类型的proto⽂件,并使⽤protobuf命令⽣成相对应的代码⽂件。
    msg.proto , 然后形成相应的.cc和.h文件,去查看Protobuf库的使用
  • 属性:消息ID,路由主题,持久化模式标志
  • 消息内容
  • 有效标志(持久化需要)
  • 持久化位置(内存中)
  • 持久化消息⻓度(内存中)
cpp 复制代码
syntax = "proto3";

package nzq;

enum ExchangeType
{
    UNKNOWTYPE = 0;
    DIRECT = 1;//直接交换 
    FANOUT = 2;//⼴播交换 
    TOPIC = 3;//主题交换 
};

enum DeliveryMode
{
    UNKNOWMODE = 0;
    UNDURABLE = 1;
    DURABLE = 2;
};

message BasicProperties
{
    string id = 1; //消息ID 
    DeliveryMode delivery_mode = 2; //持久化模式 1-⾮持久化; 2-持久化
    string routing_key = 3; //与routing_key做匹配 
};

message Message {
    message Payload {
        BasicProperties properties = 1;//消息属性 
        string body = 2;//有效载荷数据
        string valid = 3; 消息是否有效位 
    }; 
        Payload payload = 1;//真正持久化的只有这⼀个字段 
        uint64 offset = 2;//这两个字段⽤于记录消息在持久化⽂件中的位置和⻓度 
        uint64 length = 3;//以便于在加载时可以在指定位置读取指定⻓度的数据获取到消息 
};
  1. 消息的持久化管理

管理数据

  • i. 队列消息⽂件存储的路径
  • ii. 队列消息的存储⽂件名
  • iii. 队列消息的临时交换⽂件名

管理操作

  • i. ⽇志消息存储在⽂件中(4B⻓度+(属性+内容+有效位)序列化消息,连续存储即可)
  • ii. 提供队列消息⽂件创建/删除功能
  • iii. 提供队列消息的新增持久化/删除持久化
  • iv. 提供持久化内容的垃圾回收(其实就是重新加载出所有有效消息返回,并重新⽣成新的消息存储⽂件)
cpp 复制代码
    #define DATAFILE_SUBFIX ".mqd"
    #define TMPFILE_SUBFIX ".mqd.tmp"
    using MessagePtr = std::shared_ptr<nzq::Message>;
    class MessageMapper {
        public:
            MessageMapper(std::string &basedir, const std::string &qname):
                _qname(qname) {
                if (basedir.back() != '/') basedir.push_back('/');
                _datafile = basedir + qname + DATAFILE_SUBFIX;
                _tmpfile = basedir + qname + TMPFILE_SUBFIX;
                if (FileHelper(basedir).exists() == false) {
                    assert(FileHelper::createDirectory(basedir));
                }
                createMsgFile();
            }
            bool createMsgFile() {
                if (FileHelper(_datafile).exists() == true) {
                    return true;
                }
                bool ret = FileHelper::createFile(_datafile);
                if (ret == false) {
                    DLOG("创建队列数据文件 %s 失败!", _datafile.c_str());
                    return false;
                }
                return true;
            }
            void removeMsgFile() {
                FileHelper::removeFile(_datafile);
                FileHelper::removeFile(_tmpfile);
            }
            bool insert(MessagePtr &msg) {
                return insert(_datafile, msg);
            }
            bool remove(MessagePtr &msg) {
                //1. 将msg中的有效标志位修改为 '0'
                msg->mutable_payload()->set_valid("0");
                //2. 对msg进行序列化
                std::string body = msg->payload().SerializeAsString();
                if (body.size() != msg->length()) {
                    DLOG("不能修改文件中的数据信息,因为新生成的数据与原数据长度不一致!");
                    return false;
                }
                //3. 将序列化后的消息,写入到数据在文件中的指定位置(覆盖原有的数据)
                FileHelper helper(_datafile);
                bool ret = helper.write(body.c_str(), msg->offset(), body.size());
                if (ret == false) {
                    DLOG("向队列数据文件写入数据失败!");
                    return false;
                }
                //DLOG("确认消息后,删除持久化消息成功:%s", msg->payload().body().c_str());
                return true;
            }
            std::list<MessagePtr> gc() {
                bool ret;
                std::list<MessagePtr> result;
                ret = load(result);
                if (ret == false) {
                    DLOG("加载有效数据失败!\n");
                    return result;
                }
                //DLOG("垃圾回收,得到有效消息数量:%d", result.size());
                //2. 将有效数据,进行序列化存储到临时文件中
                FileHelper::createFile(_tmpfile);
                for (auto &msg : result) {
                    DLOG("向临时文件写入数据: %s", msg->payload().body().c_str());
                    ret = insert(_tmpfile, msg);
                    if (ret == false) {
                        DLOG("向临时文件写入消息数据失败!!");
                        return result;
                    }
                }
                //DLOG("垃圾回收后,向临时文件写入数据完毕,临时文件大小: %ld", FileHelper(_tmpfile).size());
                //3. 删除源文件
                ret = FileHelper::removeFile(_datafile);
                if (ret == false) {
                    DLOG("删除源文件失败!");
                    return result;
                }
                //4. 修改临时文件名,为源文件名称
                ret = FileHelper(_tmpfile).rename(_datafile);
                if (ret == false) {
                    DLOG("修改临时文件名称失败!");
                    return result;
                }
                //5. 返回新的有效数据
                return result;
            }
        private:
            bool load(std::list<MessagePtr> &result) {
                //1. 加载出文件中所有的有效数据;  存储格式 4字节长度|数据|4字节长度|数据.....
                FileHelper data_file_helper(_datafile);
                size_t offset = 0,msg_size;
                size_t fsize = data_file_helper.size();
                //DLOG("准备开始加载持久化数据,当前文件大小: %ld", data_file_helper.size());
                bool ret;
                while(offset < fsize) {
                    ret = data_file_helper.read((char*)&msg_size, offset, sizeof(size_t));
                    if (ret == false) {
                        DLOG("读取消息长度失败!");
                        return false;
                    }
                    offset += sizeof(size_t);
                    std::string msg_body(msg_size, '\0');
                    data_file_helper.read(&msg_body[0], offset, msg_size);
                    if (ret == false) {
                        DLOG("读取消息数据失败!");
                        return false;
                    }
                    offset += msg_size;
                    MessagePtr msgp = std::make_shared<Message>();
                    msgp->mutable_payload()->ParseFromString(msg_body);
                    //DLOG("加载到有效数据:%s", msgp->payload().body().c_str());
                    //如果是无效消息,则直接处理下一个
                    if (msgp->payload().valid() == "0")  {
                        DLOG("加载到无效消息:%s", msgp->payload().body().c_str());
                        continue;
                    }
                    //有效消息则保存起来
                    result.push_back(msgp);
                }
                return true;
            }
            bool insert(const std::string &filename, MessagePtr &msg) {
                //新增数据都是添加在文件末尾的
                //1. 进行消息的序列化,获取到格式化后的消息
                std::string body = msg->payload().SerializeAsString();
                //2. 获取文件长度
                FileHelper helper(filename);
                size_t fsize = helper.size();
                size_t msg_size = body.size();
                //写入逻辑:1. 先写入4字节数据长度, 2, 再写入指定长度数据
                bool ret = helper.write((char*)&msg_size, fsize, sizeof(size_t));
                if (ret == false) {
                    DLOG("向队列数据文件写入数据长度失败!");
                    return false;
                }
                //3. 将数据写入文件的指定位置
                ret = helper.write(body.c_str(), fsize + sizeof(size_t), body.size());
                if (ret == false) {
                    DLOG("向队列数据文件写入数据失败!");
                    return false;
                }
                //4. 更新msg中的实际存储信息
                msg->set_offset(fsize + sizeof(size_t));
                msg->set_length(body.size());
                return true;
            }
        private:
            std::string _qname;
            std::string _datafile;
            std::string _tmpfile;
    };

消息的管理(以队列为单位进⾏管理)

队列消息管理数据

  • i. 队列名称
  • ii. 待推送消息链表
  • iii. 持久化消息hash
  • iv. 待确认消息hash
  • v. 有效消息数量
  • vi. 已经持久化消息总量
  • vii. 持久化管理句柄

队列管理操作

  • i. 新增消息
  • ii. 获取队⾸消息(获取的同时将消息加⼊待确认队列)
  • iii. 移除指定待确认消息
  • iv. 获取队列待消费&待确认消息数量
  • v. 恢复队列历史消息。
  • vi. 销毁队列所有消息
  • vii. 判断队列消息是否为空
cpp 复制代码
class QueueMessage{
        public:
            using ptr = std::shared_ptr<QueueMessage>;
            QueueMessage(std::string &basedir, const std::string &qname):_mapper(basedir, qname), 
                _qname(qname), _valid_count(0), _total_count(0) {}
            bool recovery() {
                //恢复历史消息
                std::unique_lock<std::mutex> lock(_mutex);
                _msgs = _mapper.gc();
                for (auto &msg : _msgs) {
                    _durable_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
                }
                _valid_count = _total_count = _msgs.size();
                return true;
            }
            bool insert(const BasicProperties *bp, const std::string &body, bool queue_is_durable) {
                //1. 构造消息对象
                MessagePtr msg = std::make_shared<Message>();
                msg->mutable_payload()->set_body(body);
                if (bp != nullptr) {
                    DeliveryMode mode = queue_is_durable ? bp->delivery_mode() : DeliveryMode::UNDURABLE;
                    msg->mutable_payload()->mutable_properties()->set_id(bp->id());
                    msg->mutable_payload()->mutable_properties()->set_delivery_mode(mode);
                    msg->mutable_payload()->mutable_properties()->set_routing_key(bp->routing_key());
                }else {
                    DeliveryMode mode = queue_is_durable ? DeliveryMode::DURABLE : DeliveryMode::UNDURABLE;
                    msg->mutable_payload()->mutable_properties()->set_id(UUIDHelper::uuid());
                    msg->mutable_payload()->mutable_properties()->set_delivery_mode(mode);
                    msg->mutable_payload()->mutable_properties()->set_routing_key("");
                }
                std::unique_lock<std::mutex> lock(_mutex);
                //2. 判断消息是否需要持久化
                if (msg->payload().properties().delivery_mode() == DeliveryMode::DURABLE) {
                    msg->mutable_payload()->set_valid("1");//在持久化存储中表示数据有效
                    //3. 进行持久化存储
                    bool ret = _mapper.insert(msg);
                    if (ret == false) {
                        DLOG("持久化存储消息:%s 失败了!", body.c_str());
                        return false;
                    }
                    _valid_count += 1; //持久化信息中的数据量+1
                    _total_count += 1;
                    _durable_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
                }
                //4. 内存的管理
                _msgs.push_back(msg);
                return true;
            }
            MessagePtr front(){
                std::unique_lock<std::mutex> lock(_mutex);
                if (_msgs.size() == 0) {
                    return MessagePtr();
                }
                //获取一条队首消息:从_msgs中取出数据
                MessagePtr msg = _msgs.front();
                _msgs.pop_front();
                //将该消息对象,向待确认的hash表中添加一份,等到收到消息确认后进行删除
                _waitack_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
                return msg;
            }
            //每次删除消息后,判断是否需要垃圾回收
            bool remove(const std::string &msg_id) {
                std::unique_lock<std::mutex> lock(_mutex);
                //1. 从待确认队列中查找消息
                auto it = _waitack_msgs.find(msg_id);
                if (it == _waitack_msgs.end()) {
                    DLOG("没有找到要删除的消息:%s!", msg_id.c_str());
                    return true;
                }
                //2. 根据消息的持久化模式,决定是否删除持久化信息
                if (it->second->payload().properties().delivery_mode() == DeliveryMode::DURABLE) {
                    //3. 删除持久化信息
                    _mapper.remove(it->second);
                    _durable_msgs.erase(msg_id);
                    _valid_count -= 1;//持久化文件中有效消息数量 -1
                    gc();  //内部判断是否需要垃圾回收,需要的话则回收一下
                }
                //4. 删除内存中的信息
                _waitack_msgs.erase(msg_id);
                //DLOG("确认消息后,删除消息的管理成功:%s", it->second->payload().body().c_str());
                return true;
            }
            size_t getable_count() {
                std::unique_lock<std::mutex> lock(_mutex);
                return _msgs.size();
            }
            size_t total_count() {
                std::unique_lock<std::mutex> lock(_mutex);
                return _total_count;
            }
            size_t durable_count() {
                std::unique_lock<std::mutex> lock(_mutex);
                return _durable_msgs.size();
            }
            size_t waitack_count() {
                std::unique_lock<std::mutex> lock(_mutex);
                return _waitack_msgs.size();
            }
            void clear() {
                std::unique_lock<std::mutex> lock(_mutex);
                _mapper.removeMsgFile();
                _msgs.clear();
                _durable_msgs.clear();
                _waitack_msgs.clear();
                _valid_count = 0;
                _total_count = 0;
            }
        private:
            bool GCCheck() {
                //持久化的消息总量大于2000, 且其中有效比例低于50%则需要持久化
                if (_total_count > 2000 && _valid_count * 10  / _total_count < 5) {
                    return true;
                }
                return false;
            }
            void gc() {
                //1. 进行垃圾回收,获取到垃圾回收后,有效的消息信息链表
                if (GCCheck() == false) return;
                std::list<MessagePtr> msgs = _mapper.gc();
                for (auto &msg : msgs) {
                    auto it = _durable_msgs.find(msg->payload().properties().id());
                    if (it == _durable_msgs.end()) {
                        DLOG("垃圾回收后,有一条持久化消息,在内存中没有进行管理!");
                        _msgs.push_back(msg); ///做法:重新添加到推送链表的末尾
                        _durable_msgs.insert(std::make_pair(msg->payload().properties().id(), msg));
                        continue;
                    }
                    //2. 更新每一条消息的实际存储位置
                    it->second->set_offset(msg->offset());
                    it->second->set_length(msg->length());
                }
                //3. 更新当前的有效消息数量 & 总的持久化消息数量
                _valid_count = _total_count = msgs.size();
            }
        private:
            std::mutex _mutex;
            std::string _qname;
            size_t _valid_count;
            size_t _total_count;
            MessageMapper _mapper;
            std::list<MessagePtr> _msgs;//待推送消息
            std::unordered_map<std::string, MessagePtr> _durable_msgs;//持久化消息hash
            std::unordered_map<std::string, MessagePtr> _waitack_msgs;//待确认消息hash
    };

消息的总体对外管理

  • i. 初始化新建队列的消息管理结构,并创建消息存储⽂件
  • ii. 删除队列的消息管理结构,以及消息存储⽂件
  • iii. 向指定队列新增消息
  • iv. 获取指定队列队⾸消息
  • v. 确认指定队列待确认消息(删除)
  • vi. 判断指定队列消息是否为空
cpp 复制代码
    class MessageManager {
        public:
            using ptr = std::shared_ptr<MessageManager>;
            MessageManager(const std::string &basedir): _basedir(basedir){}
            void clear() {
                std::unique_lock<std::mutex> lock(_mutex);
                for (auto &qmsg : _queue_msgs) {
                    qmsg.second->clear();
                }
            }
            void initQueueMessage(const std::string &qname) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it != _queue_msgs.end()) {
                        return ;
                    }
                    qmp = std::make_shared<QueueMessage>(_basedir, qname);
                    _queue_msgs.insert(std::make_pair(qname, qmp));
                }
                qmp->recovery();
            }
            void destroyQueueMessage(const std::string &qname) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it == _queue_msgs.end()) {
                        return ;
                    }
                    qmp = it->second;
                    _queue_msgs.erase(it);
                }
                qmp->clear();
            }
            bool insert(const std::string &qname, BasicProperties *bp, const std::string &body, bool queue_is_durable) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it == _queue_msgs.end()) {
                        DLOG("向队列%s新增消息失败:没有找到消息管理句柄!", qname.c_str());
                        return false;
                    }
                    qmp = it->second;
                }
                return qmp->insert(bp, body, queue_is_durable);
            }
            MessagePtr front(const std::string &qname) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it == _queue_msgs.end()) {
                        DLOG("获取队列%s队首消息失败:没有找到消息管理句柄!", qname.c_str());
                        return MessagePtr();
                    }
                    qmp = it->second;
                }
                return qmp->front();
            }
            void ack(const std::string &qname, const std::string &msg_id) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it == _queue_msgs.end()) {
                        DLOG("确认队列%s消息%s失败:没有找到消息管理句柄!", qname.c_str(), msg_id.c_str());
                        return ;
                    }
                    qmp = it->second;
                }
                qmp->remove(msg_id);
                return ;
            }
            size_t getable_count(const std::string &qname) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it == _queue_msgs.end()) {
                        DLOG("获取队列%s待推送消息数量失败:没有找到消息管理句柄!", qname.c_str());
                        return 0;
                    }
                    qmp = it->second;
                }
                return qmp->getable_count();
            }
            size_t total_count(const std::string &qname) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it == _queue_msgs.end()) {
                        DLOG("获取队列%s总持久化消息数量失败:没有找到消息管理句柄!", qname.c_str());
                        return 0;
                    }
                    qmp = it->second;
                }
                return qmp->total_count();
            }
            size_t durable_count(const std::string &qname) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it == _queue_msgs.end()) {
                        DLOG("获取队列%s有效持久化消息数量失败:没有找到消息管理句柄!", qname.c_str());
                        return 0;
                    }
                    qmp = it->second;
                }
                return qmp->durable_count();
            }
            size_t waitack_count(const std::string &qname) {
                QueueMessage::ptr qmp;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _queue_msgs.find(qname);
                    if (it == _queue_msgs.end()) {
                        DLOG("获取队列%s待确认消息数量失败:没有找到消息管理句柄!", qname.c_str());
                        return 0;
                    }
                    qmp = it->second;
                }
                return qmp->waitack_count();
            }
        private:
            std::mutex _mutex;
            std::string _basedir;
            std::unordered_map<std::string, QueueMessage::ptr> _queue_msgs;
    };

虚拟机管理

虚拟机模块是对上述三个数据管理模块的整合,并基于数据之间的关联关系进⾏联合操作。

  1. 定义虚拟机类包含以下成员:
  • a. 交换机数据管理模块句柄
  • b. 队列数据管理模块句柄
  • c. 绑定数据管理模块句柄
  • d. 消息数据管理模块句柄
  1. 虚拟机包含操作:
  • a. 提供声明交换机的功能(存在则OK,不存在则创建)
  • b. 提供删除交换机的功能(删除交换机的同时删除关联绑定信息)
  • c. 提供声明队列的功能(存在则OK,不存在则创建,创建的同时创建队列关联消息管理对象)
  • d. 提供删除队列的功能(删除队列的同时删除关联绑定信息,删除关联消息管理对象及队列所有
    消息)
  • e. 提供交换机-队列绑定的功能
  • f. 提供交换机-队列解绑的功能
  • g. 提供获取交换机相关的所有绑定信息功能
  • h. 提供新增消息的功能
  • i. 提供获取指定队列队⾸消息的功能
  • j. 提供消息确认删除的功能
  1. 虚拟机管理操作:
  • a. 增删查
cpp 复制代码
    class VirtualHost {
        public:
            using ptr = std::shared_ptr<VirtualHost>;
            VirtualHost(const std::string &hname, const std::string &basedir, const std::string &dbfile):
                _host_name(hname),
                _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);
                }
            }
            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);
                return _emp->deleteExchange(name);
            }
            bool existsExchange(const std::string &name) {
                return _emp->exists(name);
            }
            Exchange::ptr selectExchange(const std::string &ename) {
                return _emp->selectExchange(ename);
            }

            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);
                return _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) {
                return _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) {
                return _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;
    };

交换机路由管理

客⼾端将消息发布到指定的交换机,交换机这时候要考虑这条数据该放⼊到哪些与⾃⼰绑定的队列

中,⽽这个考量是通过交换机类型以及匹配规则来决定的 :

  1. ⼴播交换:直接将消息交给所有绑定的队列,⽆需匹配
  2. 直接交换:队列绑定信息中的binding_key与消息中的routing_key⼀致则匹配成功,否则失败。
  3. 主题交换:只有匹配队列主题的消息才会被放⼊队列中
    其中⼴播交换和直接交换,都⾮常简单,唯⼀较为难以理解的是主题交换。


原理如图示:






cpp 复制代码
class Router {
        public:
            static bool isLegalRoutingKey(const std::string &routing_key)
            {
                //routing_key:只需要判断是否包含有非法字符即可, 合法字符( a~z, A~Z, 0~9, ., _)
                for (auto &ch : routing_key)
                {
                    if ((ch >= 'a' && ch <= 'z') ||
                        (ch >= 'A' && ch <= 'Z') ||
                        (ch >= '0' && ch <= '9') ||
                        (ch == '_' || ch == '.')) {
                        continue;
                    }
                    return false;
                }
                return true;
            }

            static bool isLegalBindingKey(const std::string &binding_key)
            {
                //1. 判断是否包含有非法字符, 合法字符:a~z, A~Z, 0~9, ., _, *, #
                for (auto &ch : binding_key) 
                {
                    if ((ch >= 'a' && ch <= 'z') ||
                        (ch >= 'A' && ch <= 'Z') ||
                        (ch >= '0' && ch <= '9') ||
                        (ch == '_' || ch == '.') ||
                        (ch == '*' || ch == '#')) {
                        continue;
                    }
                    return false;
                }
                //2. *和#必须独立存在:  news.music#.*.#
                std::vector<std::string> sub_words;
                StrHelper::split(binding_key,".",sub_words);
                for (std::string &word : sub_words) {
                    if (word.size() > 1 && 
                        (word.find("*") != std::string::npos ||
                        word.find("#") != std::string::npos)) {
                        return false;
                    }
                }
                //3. *和#不能连续出现
                for (int i = 1; i < sub_words.size(); i++) {
                    if (sub_words[i] == "#" && sub_words[i - 1] == "*") {
                        return false;
                    }
                    if (sub_words[i] == "#" && sub_words[i - 1] == "#") {
                        return false;
                    }
                    if (sub_words[i] == "*" && sub_words[i - 1] == "#") {
                        return false;
                    }
                }
                return true;
            }

            static bool route(ExchangeType type, const std::string &routing_key, const std::string &binding_key)
            {
                if (type == ExchangeType::DIRECT) {
                    return (routing_key == binding_key);
                }else if (type == ExchangeType::FANOUT) {
                    return true;
                }

                //主题交换:要进行模式匹配    news.#   &   news.music.pop
                //1. 将binding_key与routing_key进行字符串分割,得到各个的单词数组
                std::vector<std::string> bkeys, rkeys;
                int n_bkey = StrHelper::split(binding_key, ".", bkeys);
                int n_rkey = StrHelper::split(routing_key, ".", rkeys);
                //2. 定义标记数组,并初始化[0][0]位置为true,其他位置为false
                std::vector<std::vector<bool>> dp(n_bkey + 1, std::vector<bool>(n_rkey + 1, false));
                dp[0][0] = true;
                //3. 如果binding_key以#起始,则将#对应行的第0列置为1.
                for(int i = 1; i <= bkeys.size(); i++) {
                    if (bkeys[i - 1] == "#") {
                        dp[i][0] = true;
                        continue;
                    }
                    break;
                }

                 //4. 使用routing_key中的每个单词与binding_key中的每个单词进行匹配并标记数组
                for (int i = 1; i <= n_bkey; i++) {
                    for (int j = 1; j <= n_rkey; j++) {
                        //如果当前bkey是个*,或者两个单词相同,表示单词匹配成功,则从左上方继承结果
                        if (bkeys[i - 1] == rkeys[j - 1] || bkeys[i - 1] == "*") {
                            dp[i][j] = dp[i - 1][j - 1];
                        }else if (bkeys[i - 1] == "#") {
                            //如果当前bkey是个#,则需要从左上,左边,上边继承结果
                            dp[i][j] = dp[i - 1][j - 1] | dp[i][j - 1] | dp[i - 1][j];
                        }
                    }
                }

                return dp[n_bkey][n_rkey];
            }
    };

队列消费者/订阅者管理

客⼾端这边每当发起⼀个订阅请求,意味着服务器这边就多了⼀个订阅者(处理消息的客⼾端描述),⽽

这个消费者或者说订阅者它是和队列直接关联的,因为订阅请求中会描述当前⽤⼾想要订阅哪⼀个队

列的消息。

⽽⼀个信道关闭的时候,或者队列被删除的时候,那么这个信道或队列关联的消费者也就没有存在的

意义了,因此也需要将相关的消费者信息给删除掉。

基于以上需求,因此需要对订阅者信息进⾏管理。

  1. 定义消费者信息结构
  • a. 消费者标识
  • b. 订阅的队列名称
  • c. ⼀个消息的处理回调函数(实现的是当发布⼀条消息到队列,则选择消费者进⾏消费,如何消费?对于服务端来说就是调⽤这个个回调函数进⾏处理,其内部逻辑就是找到消费者对应的连接,然后将数据发送给消费者对应的客⼾端)
    i. void(const std::string&, const BasicProperties&, const std::string&)
  • d. 是否⾃动应答标志。(⼀个消息被消费者消费后,若⾃动应答,则直接移除待确认消息,否则等待客⼾端确认)
cpp 复制代码
    using ConsumerCallback = std::function<void(const std::string, const BasicProperties *bp, const std::string)>;
    struct Consumer
    {
        using ptr = std::shared_ptr<Consumer>;
        std::string tag;   // 消费者标识
        std::string qname; // 消费者订阅的队列名称
        bool auto_ack;     // 自动确认标志
        ConsumerCallback callback;

        Consumer()
        {
            DLOG("new Consumer: %p", this);
        }

        Consumer(const std::string &ctag, const std::string &queue_name, bool ack_flag, const ConsumerCallback &cb) : tag(ctag), qname(queue_name), auto_ack(ack_flag), callback(std::move(cb))
        {
            DLOG("new Consumer: %p", this);
        }

        ~Consumer()
        {
            DLOG("del Consumer: %p", this);
        }
    };
  1. 消费者管理--以队列为单元进⾏管理-队列消费者管理结构

a. 操作:

  • i. 新增消费者:信道提供的服务是订阅队列消息的时候创建
  • ii. 删除消费者:取消订阅/信道关闭/连接关闭的时候删除
  • iii. 获取消费者:从队列所有的消费者中按序取出⼀个消费者进⾏消息的推送
  • iv. 判断队列消费者是否为空
  • v. 判断指定消费者是否存在
  • vi. 清理队列所有消费者

b. 元素

  • i. 消费者管理结构:vector
  • ii. 轮转序号:⼀个队列可能会有多个消费者,但是⼀条消息只需要被⼀个消费者消费即可,因此采⽤RR轮转
  • iii. 互斥锁:保证线程安全
  • iv. 队列名称
cpp 复制代码
    // 以队列为单元的消费者管理结构
    class QueueConsumer
    {
    public:
        using ptr = std::shared_ptr<QueueConsumer>;
        QueueConsumer(const std::string &qname) : _qname(qname), _rr_seq(0) {}
        // 队列新增消费者
        Consumer::ptr create(const std::string &ctag, const std::string &queue_name, bool ack_flag, const ConsumerCallback &cb)
        {
            // 1. 加锁
            std::unique_lock<std::mutex> lock(_mutex);
            // 2. 判断消费者是否重复
            for (auto &consumer : _consumers)
            {
                if (consumer->tag == ctag)
                {
                    return Consumer::ptr();
                }
            }
            // 3. 没有重复则新增--构造对象
            auto consumer = std::make_shared<Consumer>(ctag, queue_name, ack_flag, cb);
            // 4. 添加管理后返回对象
            _consumers.push_back(consumer);
            return consumer;
        }

        // 队列移除消费者
        void remove(const std::string &ctag)
        {
            // 1. 加锁
            std::unique_lock<std::mutex> lock(_mutex);
            // 2. 遍历查找-删除
            for (auto it = _consumers.begin(); it != _consumers.end(); ++it)
            {
                if ((*it)->tag == ctag)
                {
                    _consumers.erase(it);
                    return;
                }
            }
            return;
        }
        // 队列获取消费者:RR轮转获取
        Consumer::ptr choose()
        {
            // 1. 加锁
            std::unique_lock<std::mutex> lock(_mutex);
            if (_consumers.size() == 0)
            {
                return Consumer::ptr();
            }
            // 2. 获取当前轮转到的下标
            int idx = _rr_seq % _consumers.size();
            _rr_seq++;
            // 3. 获取对象,返回
            return _consumers[idx];
        }
        // 是否为空
        bool empty()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            return _consumers.size() == 0;
        }
        // 判断指定消费者是否存在
        bool exists(const std::string &ctag)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 2. 遍历查找
            for (auto it = _consumers.begin(); it != _consumers.end(); ++it)
            {
                if ((*it)->tag == ctag)
                {
                    return true;
                }
            }
            return false;
        }
        // 清理所有消费者
        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _consumers.clear();
            _rr_seq = 0;
        }

    private:
        std::string _qname;
        std::mutex _mutex;
        uint64_t _rr_seq; // 轮转序号
        std::vector<Consumer::ptr> _consumers;
    };
  1. 对消费者进⾏统⼀管理结构
  • a. 初始化/删除队列的消费者信息结构(创建/删除队列的时候初始化)
  • b. 向指定队列新增消费者(客⼾端订阅指定队列消息的时候):新增完成的时候返回消费者对象
  • c. 从指定队列移除消费者(客⼾端取消订阅的时候)
  • d. 移除指定队列的所有消费者(队列被删除时销毁):删除消费者的队列管理单元对象
  • e. 从指定队列获取⼀个消费者(轮询获取-消费者轮换消费起到负载均衡的作⽤)
  • f. 判断队列中消费者是否为空
  • g. 判断队列中指定消费者是否存在
  • h. 清理所有消费者
cpp 复制代码
    class ConsumerManager
    {
    public:
        using ptr = std::shared_ptr<ConsumerManager>;
        ConsumerManager() {}
        void initQueueConsumer(const std::string &qname)
        {
            // 1. 加锁
            std::unique_lock<std::mutex> lock(_mutex);
            // 2. 重复判断
            auto it = _qconsumers.find(qname);
            if (it != _qconsumers.end())
            {
                return;
            }
            // 3. 新增
            auto qconsumers = std::make_shared<QueueConsumer>(qname);
            _qconsumers.insert(std::make_pair(qname, qconsumers));
        }
        void destroyQueueConsumer(const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _qconsumers.erase(qname);
        }
        Consumer::ptr create(const std::string &ctag, const std::string &queue_name, bool ack_flag, const ConsumerCallback &cb)
        {
            // 获取队列的消费者管理单元句柄,通过句柄完成新建
            QueueConsumer::ptr qcp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _qconsumers.find(queue_name);
                if (it == _qconsumers.end())
                {
                    DLOG("没有找到队列 %s 的消费者管理句柄!", queue_name.c_str());
                    return Consumer::ptr();
                }
                qcp = it->second;
            }
            return qcp->create(ctag, queue_name, ack_flag, cb);
        }
        void remove(const std::string &ctag, const std::string &queue_name)
        {
            QueueConsumer::ptr qcp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _qconsumers.find(queue_name);
                if (it == _qconsumers.end())
                {
                    DLOG("没有找到队列 %s 的消费者管理句柄!", queue_name.c_str());
                    return;
                }
                qcp = it->second;
            }
            return qcp->remove(ctag);
        }
        Consumer::ptr choose(const std::string &queue_name)
        {
            QueueConsumer::ptr qcp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _qconsumers.find(queue_name);
                if (it == _qconsumers.end())
                {
                    DLOG("没有找到队列 %s 的消费者管理句柄!", queue_name.c_str());
                    return Consumer::ptr();
                }
                qcp = it->second;
            }
            return qcp->choose();
        }
        bool empty(const std::string &queue_name)
        {
            QueueConsumer::ptr qcp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _qconsumers.find(queue_name);
                if (it == _qconsumers.end())
                {
                    DLOG("没有找到队列 %s 的消费者管理句柄!", queue_name.c_str());
                    return false;
                }
                qcp = it->second;
            }
            return qcp->empty();
        }
        bool exists(const std::string &ctag, const std::string &queue_name)
        {
            QueueConsumer::ptr qcp;
            {
                std::unique_lock<std::mutex> lock(_mutex);
                auto it = _qconsumers.find(queue_name);
                if (it == _qconsumers.end())
                {
                    DLOG("没有找到队列 %s 的消费者管理句柄!", queue_name.c_str());
                    return false;
                }
                qcp = it->second;
            }
            return qcp->exists(ctag);
        }
        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _qconsumers.clear();
        }

    private:
        std::mutex _mutex;
        std::unordered_map<std::string, QueueConsumer::ptr> _qconsumers;
    };
相关推荐
回家路上绕了弯18 小时前
深入解析Agent Subagent架构:原理、协同逻辑与实战落地指南
分布式·后端
用户83071968408221 小时前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
用户8307196840823 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者4 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者6 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧7 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖7 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农7 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者7 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀7 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式