完整教程:【仿RabbitMQ的发布订阅式消息队列】--- 服务端模块

Welcome to 9ilk's Code World

(๑•́ ₃ •̀๑) 个人主页:9ilk

(๑•́ ₃ •̀๑) 文章专栏:     项目


本篇博客主要是对消息队列服务端模块进行梳理。

交换机数据管理

        该模块主要是对交换机相关数据的管理,主要的是先在内存中管理,再根据是否持久化进行持久化。

1. 交换机数据类:

a. 交换机名称

b. 交换机类型

c. 交换机是否持久标志

d. 是否自动删除标志

e. 其他参数

2. 交换机数据持久化类

a. 声明/删除交换机表

b. 新增交换机数据

c. 移除交换机数据

d. 查询所有交换机数据 / 恢复历史数据(为内存管理提供的接口)

e. 查询指定交换机数据(根据名称)

3. 交换机数据内存管理类

a. 声明交换机并添加管理(存在则OK,不存在则创建)

b. 删除交换机

c. 获取指定交换机

d. 交换机是否存在的判断

e. 清理所有交换机数据

f. 交换机数量

交换机类

struct Exchange
  {
    using ptr = shared_ptr;
    //1.交换机名称
    string _name;
    //2.交换机类型
    ExchangeType _type;
    //3.交换机持久化标志
    bool _durable;
    //4.是否自动删除标志
    bool _auto_delete;
    //5.其他参数
    google::protobuf::Map _args;
    //提供一个默认构造
    Exchange()
    {}
    Exchange(const string& ename,ExchangeType etype,bool edurable,
            bool eauto_delete,
            const google::protobuf::Map& eargs):
            _name(ename),_type(etype),_durable(edurable),_auto_delete(eauto_delete),
            _args(eargs)
    {}
    //args存储键值对,在存储数据库的时候会组织一个格式字符串进行存储 key=val&key=val...
    void setArgs(const string& str_args)//内部解析str_args字符串将内容存储到成员中
    {
       //1.按照&分割每一组参数kv
       vector result;
       StringHelper::SplitStr(str_args,"&",result);
       //2.将每一组按照=分割
       for(auto& str : result)
       {   //key=val
          size_t pos = str.find("=");
          string key = str.substr(0,pos);
          string val = str.substr(pos+1);
          _args[key] = val;
       }
    }
    string getArgs() //将args中的内容进行序列化后,返回一个字符串
    {
       stringstream ss;
       for(auto& pr : _args)
           ss << pr.first << "=" << pr.second << "&";
        return ss.str();
    }
  };

交换机数据持久化类

        数据持久化我们这里采取的是对交换机的数据持久化到数据库中,同时既然持久化了,到时服务器重启的时候,我们需要借助持久化的数据恢复内存中的数据继续提供服务,因此我们还需要提供一个recovery()接口来恢复历史数据

 using ExchangeMap = std::unordered_map;
  class ExchangeMapper
  {
    public:
      ExchangeMapper(const string& dbfile)
      :_sql_helper(dbfile)
      {
          //1.目录
          string parentDir = FileHelper::parentDirectory(dbfile);
          FileHelper::createDirectory(parentDir);
          //2.文件
          assert(_sql_helper.open());
          //3.创建表
          createTable();
      }
      //声明交换机表
      void createTable()
      {  //1.编写创建表语句
        #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) //创建表失败没必要再继续看
        {
          ERR_LOG("创建交换机数据表失败!");
          abort();
        }
        return;
      }
      //删除交换机表
      void removeTable()
      {
        #define REMOVE_TABLE "drop table if exists exchange_table"
         bool ret = _sql_helper.exec(REMOVE_TABLE,nullptr,nullptr);
         if(ret == false) //创建表失败没必要再继续看
         {
           ERR_LOG("删除交换机数据表失败!");
           abort();
         }
         return;
      }
      //新增交换机数据
      void insert(Exchange::ptr& exchange)
      {
         stringstream ss;
         ss << "insert into  exchange_table values (";
         ss << "'" << exchange->_name << "',";
         ss << exchange->_type << ",";
         ss << exchange->_durable << ",";
         ss << exchange->_auto_delete << ",";
         ss <<  "'" << exchange->getArgs() << "');";
         if(!_sql_helper.exec(ss.str(),nullptr,nullptr))
          ERR_LOG("插入交换数据表失败!");
      }
      //移除交换机数据
      void remove(const string&name)
      {
         string sql =  "delete from exchange_table where name='" ;
         sql += name;
         sql += "';";
          if(!_sql_helper.exec(sql,nullptr,nullptr))
          ERR_LOG("删除交换机%s失败!",name.c_str());
      }
      //恢复历史数据
      ExchangeMap recovery()
      {
         ExchangeMap emp;
         string sql = "select name,type,durable,auto_delete,args from exchange_table;";
         bool ret = _sql_helper.exec(sql,selectCallback,&emp);
         if(ret == false)
         {
            ERR_LOG("恢复历史数据失败!");
            abort();
            return ExchangeMap();
         }
         return emp;
      }
    private:
    //注意回调必须设置为静态否则会多个this指针参数,无法匹配
    static int selectCallback(void* arg,int numcol,char** row,char** fields)
    {
       ExchangeMap* emptr = (ExchangeMap*)arg;
       //1.构造Exchange对象
       Exchange::ptr exchange = make_shared();
       exchange->_name = row[0];
       exchange->_type = (zmq::ExchangeType)stoi(row[1]);
       exchange->_durable = (bool)stoi(row[2]);
       exchange->_auto_delete = (bool)stoi(row[3]);
       if(row[4]) //注意这里有参数才设置
       exchange->setArgs(row[4]);
       //2.插入到ExchangeMap中
       emptr->insert(make_pair(exchange->_name,exchange));
       //3.一定返回0
       return 0;
    }
    private:
      SqLiteHelper _sql_helper;
  };

交换机数据内存管理类

        我们交换机类是有一个是否持久化标志的,因此内存管理类,势必需要一个交换机数据持久化句柄,同时内存也要管理,我们可以采用hashmap容器进行管理。

需要注意的是:

1. 在构造内存管理类时,就可以使用持久化句柄恢复历史数据。 

2. 这些操作都是需要加锁的,不然可能被多个线程操作存在线程安全问题。

3. 为了尽可能减少数据库的I/O操作提高效率,在对交换机的增删操作,我们可以现在内存中查找是否已经存在过了。

4. 判断某个交换机是否存在这个操作我们是单独提供的,但是不能在其他接口复用这个操作,否则可能产生死锁或未定义行为,因此我们需要自己单独判断,同时这部分操作需要放在临界区中,因为有可能你刚判断完也没有该交换机,后面就有线程先拿到锁进行新增,导致重复添加

  //3.交换机数据内存管理类
  class ExchangeManager
  {
    public:
      using ptr = shared_ptr;
      ExchangeManager(const string& dbfile)
      :_mapper(dbfile)
      {
          _exchanges = _mapper.recovery();
      }
      //声明交换机
      void declareExchange(const string& name,ExchangeType type,bool durable,
                      bool auto_delete,const google::protobuf::Map& args)
      {
         unique_lock lock(_mtx);
        //1.构造交换机对象
        //判断是否已经有了
          auto it = _exchanges.find(name);
          if(it != _exchanges.end()) return;
          Exchange::ptr exchange = make_shared();
          exchange->_name = name;
          exchange->_type = type;
          exchange->_durable = durable;
          exchange->_auto_delete = auto_delete;
          exchange->_args = args;
        //2.插入map进行管理
        _exchanges.insert(make_pair(name,exchange));
        //3.判断是否需要持久化
        if(durable) _mapper.insert(exchange);
      }
      //删除交换机
      void deleteExchange(const string&name)
      {
          unique_lock lock(_mtx);
          //先查是否已经存在该交换机,没有就不删除
          auto it = _exchanges.find(name);
          if(it == _exchanges.end()) return;
          //判断是否设置持久化 设置则移除数据库内该交换机数据
          if(it->second->_durable) _mapper.remove(name);
          //删除哈希表中交换机
          _exchanges.erase(name);
      }
      //判断交换机是否存在
      bool exists(const string&name)
      {
        unique_lock lock(_mtx);
        auto it = _exchanges.find(name);
        if(it == _exchanges.end()) return false;
        return true;
      }
      //清理所有交换机数据
      void clear()
      {
        unique_lock lock(_mtx);
         _exchanges.clear();
         _mapper.removeTable();//???不持久化吗
      }
      //获取指定交换机对象
      Exchange::ptr selectExchange(const string&name)
      {
          unique_lock lock(_mtx);
          //先查是否已经存在该交换机,没有就不删除
          auto it = _exchanges.find(name);
          if(it == _exchanges.end()) return nullptr;
          return it->second;
      }
      size_t size()
      {
          unique_lock lock(_mtx);
          for(auto& ex : _exchanges)
          {
             cout << ex.first << endl;
          }
          return _exchanges.size();
      }
    private:
      std::mutex _mtx;
      ExchangeMapper _mapper;
      ExchangeMap _exchanges;
  };

队列数据管理

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

队列数据类

a. 队列名称

b. 是否持久化标志

c. 是否独占标志

d. 是否自动删除标志

e. 其他参数

 struct MsgQueue
    {
       using ptr = shared_ptr;
       //队列名称
       string _name;
       //是否持久化标志
       bool _durable;
       //是否独占标志
       bool _exclusive;
       //是否自动删除标志
       bool _auto_delete;
       //其他参数
        google::protobuf::Map _args;
       MsgQueue()
       {}
       MsgQueue(const string&name,bool durable,bool exclusive,bool auto_delete,
                const  google::protobuf::Map& args):
                _name(name),_durable(durable),_exclusive(exclusive),_auto_delete(auto_delete),
                _args(args)
       {}
        //args存储键值对,在存储数据库的时候会组织一个格式字符串进行存储 key=val&key=val...
        void setArgs(const string& str_args)//内部解析str_args字符串将内容存储到成员中
        {
            //1.按照&分割每一组参数kv
            vector result;
            StringHelper::SplitStr(str_args,"&",result);
            //2.将每一组按照=分割
            for(auto& str : result)
            {   //key=val
                size_t pos = str.find("=");
                string key = str.substr(0,pos);
                string val = str.substr(pos+1);
                _args[key] = val;
            }
        }
        string getArgs() //将args中的内容进行序列化后,返回一个字符串
        {
            stringstream ss;
            for(auto& pr : _args)
                ss << pr.first << "=" << pr.second << "&";
                return ss.str();
        }
    };

队列数据持久化类

a. 创建 / 删除队列数据表

b. 新增队列数据

c. 移除队列数据

d. 查询所有队列数据

e. 查询指定队列数据(根据名称)

 using QueueMap = std::unordered_map;
    class MsgQueueMapper
    {
      public:
        using ptr = shared_ptr;
        MsgQueueMapper(const string& dbfile)
        :_sql_helper(dbfile)
        {
          //1.目录
          string parentDir = FileHelper::parentDirectory(dbfile);
          FileHelper::createDirectory(parentDir);
          //2.文件
          assert(_sql_helper.open());
          //3.创建表
          createTable();
        }
        //创建队列数据表
        void createTable()
        {
                //1.编写创建表语句
            #define CREATE_TABLE "create table if not exists queue_table(\
                                name varchar(32) primary key,\
                                durable int,\
                                exclusive int,\
                                auto_delete int,\
                                args varchar(128));"
            bool ret = _sql_helper.exec(CREATE_TABLE,nullptr,nullptr);
            if(ret == false) //创建表失败没必要再继续看
            {
                ERR_LOG("创建交换机数据表失败!");
                abort();
            }
            return;
        }
        //删除数据表
        void  removeTable()
        {
            #define REMOVE_TABLE "drop table if exists queue_table"
            bool ret = _sql_helper.exec(REMOVE_TABLE,nullptr,nullptr);
            if(ret == false) //创建表失败没必要再继续看
            {
                ERR_LOG("删除交换机数据表失败!");
                abort();
            }
            return;
        }
        //新增队列数据
        bool insert(MsgQueue::ptr& queue)
        {
            stringstream ss;
            ss << "insert into  queue_table values (";
            ss << "'" << queue->_name << "',";
            ss << queue->_durable << ",";
            ss << queue->_exclusive << ",";
            ss << queue->_auto_delete << ",";
            ss <<  "'" << queue->getArgs() << "');";
            if(!_sql_helper.exec(ss.str(),nullptr,nullptr))
            ERR_LOG("插入交换数据表失败!");
            return true; //bug:没返回true
        }
        //删除指定队列数据
        void remove(const string& name)
        {
            string sql =  "delete from queue_table where name='" ;
            sql += name;
            sql += "';";
            if(!_sql_helper.exec(sql,nullptr,nullptr))
              ERR_LOG("删除交换机%s失败!",name.c_str());
        }
        //恢复历史数据
        QueueMap recovery()
        {
            QueueMap qmp;
            string sql = "select name,durable,exclusive,auto_delete,args from queue_table;";
            bool ret = _sql_helper.exec(sql,selectCallback,&qmp);
            if(ret == false)
            {
                ERR_LOG("恢复历史数据失败!");
                abort();
                return QueueMap();
            }
            return qmp;
        }
     private:
              //注意回调必须设置为静态否则会多个this指针参数,无法匹配
        static int selectCallback(void* arg,int numcol,char** row,char** fields)
        {
            QueueMap* qmptr = (QueueMap*)arg;
            //1.构造Exchange对象
            MsgQueue::ptr queue = make_shared();
            queue->_name = row[0];
            queue->_durable = (bool)stoi(row[1]);
            queue->_exclusive = (bool)stoi(row[2]);
            queue->_auto_delete = (bool)stoi(row[3]);
            if(row[4]) //注意这里有参数才设置
            queue->setArgs(row[4]);
            //2.插入到ExchangeMap中
            qmptr->insert(make_pair(queue->_name,queue));
            //3.一定返回0
            return 0;
        }
     private:
       SqLiteHelper _sql_helper;
    };

队列数据内存管理类

a. 创建队列并添加管理(存在则OK,不存在则创建)

b. 删除队列

c. 获取指定队列

d. 获取所有队列

e. 销毁所有队列数据

   同样的,如果队列数据设置了持久化标志需要使用持久化句柄进行持久化;同时删除的时候,如果之前设置了持久化标志,也是要从数据库删除。

 class MsgQueueManager
    {
    public:
        using ptr = shared_ptr;
        MsgQueueManager(const string& dbfile)
        :_mapper(dbfile)
        {
             _msg_queues = _mapper.recovery();
        }
        //1.声明队列
        bool declareQueue(const string& qname,bool qdurable,bool qexclusive,
                         bool qauto_delete, const google::protobuf::Map& args)
        {
            unique_lock lock(_mtx);
            //1.构造交换机对象
            //判断是否已经有了
            auto it = _msg_queues.find(qname);
            if(it != _msg_queues.end()) return true;
            MsgQueue::ptr queue = make_shared(qname,qdurable,qexclusive,qauto_delete,args);
            //2.插入map进行管理
            _msg_queues.insert(make_pair(qname,queue));
            //3.判断是否需要持久化
            if(qdurable)
            {
                bool ret = _mapper.insert(queue);
                if(!ret)
                {
                    ERR_LOG("持久化失败")
                    return false;
                }
            }
            return true;
        }
        //2.删除队列
        void deleteQueue(const string& name)
        {
          unique_lock lock(_mtx);
          //先查是否已经存在该交换机,没有就不删除
          auto it = _msg_queues.find(name);
          if(it == _msg_queues.end()) return;
          //判断是否设置持久化 设置则移除数据库内该交换机数据
          if(it->second->_durable) _mapper.remove(name);
          //删除哈希表中交换机
          _msg_queues.erase(name);
        }
        //3.获取指定队列
        MsgQueue::ptr selectQueue(const string& name)
        {
            unique_lock lock(_mtx);
            //先查是否已经存在该交换机,没有就不删除
            auto it = _msg_queues.find(name);
            if(it == _msg_queues.end()) return nullptr;
            return it->second;
        }
        //4.获取所有队列
        QueueMap allQueues()
        {
            unique_lock lock(_mtx);
            return _msg_queues;
        }
        //指定队列是否存在
        bool exists(const string& name)
        {
            unique_lock lock(_mtx);
            auto it = _msg_queues.find(name);
            if(it == _msg_queues.end()) return false;
            return true;
        }
        //队列个数
        size_t size()
        {
            unique_lock lock(_mtx);
            return _msg_queues.size();
        }
        //清空
        void clear()
        {
            unique_lock lock(_mtx);
            _msg_queues.clear();
            _mapper.removeTable();
        }
      private:
        std::mutex _mtx;
        MsgQueueMapper _mapper;
        QueueMap _msg_queues;
    };

绑定数据管理

        绑定信息本质上就是一个交换机关联了哪些队列的模块。

绑定信息类

a. 交换机名称

b. 队列名称

c. binding_key(分发匹配规则 --- 决定了哪些数据能被交换机放入队列)

 //绑定信息类
    struct Binding
    {
        using ptr = shared_ptr;
        string _exchange_name;//交换机名称
        string _msgqueue_name;//队列名称
        string _binding_key; //绑定信息
        Binding();
        Binding(const string& ename,const string& qname, const string& key)
        :_exchange_name(ename),_msgqueue_name(qname),_binding_key(key)
        {}
    };

绑定信息持久化类

a. 创建 / 删除绑定信息数据表

b. 新增绑定信息数据

c. 移除指定绑定信息数据

d. 移除指定交换机相关绑定信息数据 : 移除交换机的时候会被调用

e. 移除指定队列相关绑定信息数据:移除队列的时候会被调用

f. 查询所有绑定信息数据:用于重启服务器时进行历史数据恢复

g. 查询指定绑定信息数据 (根据交换机-队列名称)

    using MsgQueueBindMap = std::unordered_map; //队列名称,绑定信息
    using BindingMap = std::unordered_map;
    class BindingMapper
    {
      public:
         BindingMapper(const string& dbfile)
         :_sql_helper(dbfile)
         {
             //看数据文件的目录是否创建
             string parentDir = FileHelper::parentDirectory(dbfile);
             FileHelper::createDirectory(parentDir);
             //打开数据库文件
             assert(_sql_helper.open());
             //创建表
             createTable();
         }
         void createTable()
         {
            stringstream sql;
            sql << "create table if not exists binding_table(";
            sql << "exchange_name varchar(32)," ;
            sql << "queue_name varchar(32),";
            sql << "binding_key varchar(128));";
            assert(_sql_helper.exec(sql.str(),nullptr,nullptr));
         }
         void removeTable()
         {
            string sql =  "drop table if exists binding_table; ";
            assert(_sql_helper.exec(sql,nullptr,nullptr));
         }
         //新增绑定信息
         bool insert(Binding::ptr& binding)
         {
            //1.根据绑定信息对象编写sql语句
            stringstream sql;
            sql << "insert into binding_table values(";
            sql << "'" <_exchange_name << "',";
            sql << "'" << binding->_msgqueue_name << "',";
            sql << "'" << binding->_binding_key << "');";
            //2.执行语句
            return _sql_helper.exec(sql.str(),nullptr,nullptr);
         }
         //根据交换机-队列移除指定绑定信息
         void remove(const string& ename,const string& qname)
         {
            stringstream sql;
            sql << "delete from binding_table where exchange_name='";
            sql << ename << "' and queue_name='";
            sql << qname << "';";
            _sql_helper.exec(sql.str(),nullptr,nullptr);
         }
         //移除指定交换机的所有绑定信息
         void removeExchangeBinding(const string& ename)
         {
            stringstream sql;
            sql << "delete from binding_table where exchange_name='";
            sql <<  ename << "';";
            _sql_helper.exec(sql.str(),nullptr,nullptr);
         }
         //移除指定队列的绑定信息
         void removeMsgQueueBinding(const string& qname)
         {
            stringstream sql;
            sql << "delete from binding_table where queue_name='";
            sql << qname <<"'" << ";";
            _sql_helper.exec(sql.str(),nullptr,nullptr);
         }
         //恢复历史数据
         BindingMap recovery()
         {
            string sql = "select exchange_name,queue_name,binding_key from binding_table;";
            BindingMap bmp;
            _sql_helper.exec(sql,selectCallback,&bmp);
            return bmp;
         }
      private:
         static int selectCallback(void* arg,int numcol,char** row,char** fields)
         {
            //注意这里我们需要先使用引用获取MsgQueueMap否则根本没有插入数据,而且是[]
            BindingMap* bmptr = (BindingMap*)arg;
            MsgQueueBindMap& mqbp = (*bmptr)[row[0]]; //不存在则创建
            Binding::ptr bp = make_shared(row[0],row[1],row[2]);
            mqbp[bp->_msgqueue_name] = bp;
            return 0;
         }
      private:
         SqLiteHelper _sql_helper;
    };
  • 队列与绑定信息是一一对应的(因为是给某个交换机绑定队列),因此一个交换机可能会有多个队列的绑定信息
  • 基于这个思想,我们先定义一个队列名,与绑定信息binding_key的映射关系MsgQueueBindMap,这样能方便通过队列名查找binding_key;然后定义一个交换机名称与队列绑定信息的映射关系BindingMap ,这个map中包含了所有绑定信息,并且以交换机为单元进行区分。
  • 如果是建立两个map,即交换机与绑定信息的映射&&队列与绑定信息的映射,在删除交换机相关绑定信息的时候,不仅要删除交换机映射,还要删除对应队列中中的映射,否则对象得不到释放,这样是比较麻烦的。

绑定信息内存管理类

a. 创建绑定信息,并添加管理(存在则OK,不存在则创建)

b. 解除指定的绑定信息

c. 删除指定队列的所有绑定信息

d. 删除交换机相关的所有绑定信息

e. 获取交换机相关的 所有绑定信息 : 交换机收到消息后,需要分发给自己队列

f. 判断指定绑定信息是否存在

g. 获取当前绑定信息数量

h. 销毁所有绑定信息数据

  class BindingManager
    {
     public:
       using ptr = shared_ptr;
       BindingManager(const string& dbfile)
       :_mapper(dbfile)
       {
          _bindings = _mapper.recovery();
       }
       //新增一条绑定
       bool bind(const string& ename,const string& qname,const string& key,bool durable)
       {
          unique_lock lock(_mtx);
          //1.先查找是否已经新增绑定了
          auto it = _bindings.find(ename);
          if(it != _bindings.end() && it->second.find(qname) != it->second.end())
              return true;
          //2.构造绑定信息对象:绑定信息是否需要持久化 取决于交换机数据是否持久化
          Binding::ptr bp = make_shared(ename,qname,key);
          if(durable) //交换机数据持久化那么绑定信息也要持久化
          {
            bool ret = _mapper.insert(bp);
            if(!ret) return false;
          }
          //3.将绑定信息添加到内存管理
          auto& qbmp = _bindings[ename];//注意这里用的是引用
          qbmp.insert(make_pair(qname,bp));
          return true;
       }
       //移除绑定
       void unBind(const string& ename,const string& qname)
       {
           unique_lock lock(_mtx);
           //1.查找交换机
           auto eit = _bindings.find(ename);
           if(eit == _bindings.end()) return;//没有交换机绑定信息
           //2.查找队列
           auto qit = eit->second.find(qname);
           if(qit == eit->second.end()) return;
           //3.查找到
           eit->second.erase(qname);
           //注意数据里的也要删除
           _mapper.remove(ename,qname);
        }
       //移除指定交换机的绑定信息
       void removeExchangeBindings(const string& ename)
       {
           unique_lock lock(_mtx);
           auto eit = _bindings.find(ename);
           if(eit == _bindings.end()) return;//没有交换机绑定信息
           _bindings.erase(ename);
           //注意数据库里的也要删除
           _mapper.removeExchangeBinding(ename);
       }
       //移除指定队列的绑定信息
       void removeMsgQueueBindings(const string& qname)
       {
          unique_lock lock(_mtx);
          auto start = _bindings.begin();
          for(;start != _bindings.end() ;start++)
             start->second.erase(qname);
           _mapper.removeMsgQueueBinding(qname);
       }
       //获取指定交换机的绑定信息
       MsgQueueBindMap getExchangeBindings(const string& ename)
       {
           unique_lock lock(_mtx);
           auto eit = _bindings.find(ename);
           if(eit == _bindings.end()) return MsgQueueBindMap();//没有交换机绑定信息
           return eit->second;
        }
       //获取指定队列的绑定信息
       Binding::ptr getBinding(const string& ename,const string& qname)
       {
           unique_lock lock(_mtx);
           //1.查找交换机
           auto eit = _bindings.find(ename);
           if(eit == _bindings.end()) return nullptr;//没有交换机绑定信息
           //2.查找队列
           auto qit = eit->second.find(qname);
           if(qit == eit->second.end()) return nullptr;
           //3.查找到
           return qit->second;
       }
       //
       bool exists(const string& ename,const string& qname)
       {
           unique_lock lock(_mtx);
           //1.查找交换机
           auto eit = _bindings.find(ename);
           if(eit == _bindings.end()) return false;//没有交换机绑定信息
           //2.查找队列
           auto qit = eit->second.find(qname);
           if(qit == eit->second.end()) return false;
           //3.查找到
           return true;
       }
       //绑定信息的个数
       size_t size()
       {
           unique_lock lock(_mtx);
           size_t total = 0;
            auto start = _bindings.begin();
          for(;start != _bindings.end() ;start++)
              total += start->second.size();
           return total;
       }
       //
       void clear()
       {
           unique_lock lock(_mtx);
           _bindings.clear();
           _mapper.removeTable();
       }
    private:
       std::mutex _mtx;
       BindingMapper _mapper;
       BindingMap _bindings;
    };

消息管理

我们的消息是有持久化的需求的,因为有可能你服务器运行期间暂时没有消费者来消费这条消息,等到下次启动时有人来消费时,就需要根据持久化的数据恢复历史消息。

消息的持久化管理,我们不使用数据库,因为有些消息庞大不适合用数据库存储,其次消息的持久化是为了备份,不是为了查询,因此直接使用普通文件进行存储即可。具体的方案如下:

1. 以队列为单元进行持久化管理:即每个队列都要有一个自己的数据文件。当对消息文件进行垃圾回收时,需要重新加载所有的有效消息重新生成新的数据文件,但是这会导致消息的存储位置改变,所有队列的消息位置都会变 , 需要更新内存中的数据,此时就需要将所有队列数据进行加锁("全局锁",阻塞所有队列,并发性能极差),然后进行更新,这无疑导致锁冲突频繁效率较低,因此如果每个队列都有自己的独立数据文件,每次只需要对操作的队列数据进行更新即可。

2. 消息长度(4字节)+ 消息主题(若干字节):数据存储在文件中就需要约定格式,通过描述消息实际存储长度来解决粘包问题。

向外提供的操作:

  1. 消息文件的创建与删除
  2. 消息的新增持久化 / 删除消息的持久化(并不是真正删除只是将标志位置为无效
  3. 历史数据恢复 / 垃圾回收

垃圾回收的时机:因为每次删除数据都不是真正删除,因此文件中的数据会越来越多。但是也不是每次删除都需要回收,当文件中有效消息超过两千条,且其中有效消息比例低于50%就进行回收。

垃圾回收的思想:先删除原文件再将数据写入新的数据文件,这无疑是不安全的,因为可能写一半时发生故障(比如进程被kill掉、文件系统崩溃、机器断电等)都会让新文件不完整,而旧文件已经被删除,导致数据丢失。真正安全的做法是先提取出有效的消息链表,然后写入到一个临时文件中,写完再删除源文件,将临时文件名改为源文件名(重命名是原子的),最后返回有效消息链表。

消息持久化管理

需要管理的数据:

  1. 队列名
  2. 根据队列名生成数据文件名称:队列名.mqd
  3. 根据队列名生成临时文件名称:队列名.mqd.tmp
    #define DATAFILLE_SUBFIX ".mqd" //数据文件后缀
    #define TMPFILE_SUBFIX ".mqd.tmp" //临时文件后缀
    using MessagePtr = shared_ptr;
    class MessageMapper
    {
     public:
         MessageMapper(string& basedir,const string&qname)
         :_qname(qname)
         {
            //1.是否最后是路径分割符
             if(basedir.back() != '/') basedir += '/';
            //2.生成数据文件名和临时文件名
            _datafile = basedir + qname + DATAFILLE_SUBFIX;
            _tmpfile = basedir + qname + TMPFILE_SUBFIX;
            //3.判断basedir目录是否创建
            if(FileHelper(basedir).exists() == false)
                  assert(FileHelper::createDirectory(basedir));
            //4.创建数据文件
            assert(createMsgFile());
         }
         //创建消息文件
         bool createMsgFile()
         {
             if(FileHelper(_datafile).exists() == true)
                 return true;
             bool ret = FileHelper::createFile(_datafile);
             if(ret == false)
             {
                 ERR_LOG("创建数据文件%s失败!",_datafile.c_str());
                 return false;
             }
             return true;
         }
         //删除消息文件:注意不删除basedir,因为可能有其他的队列数据文件
         void removeMsgFile()
         {
            //1.删除数据文件
            FileHelper::removeFile(_datafile);
            //2.删除临时文件
            FileHelper::removeFile(_tmpfile);
         }
         //消息的新增持久化
         bool insert(MessagePtr& msg)
         {
           return insert(_datafile,msg);
         }
         //消息的删除持久化
         bool remove( MessagePtr& msg)
         {
            //1.将文件中的有效标记位设置为'0'
            msg->mutable_payload()->set_valid("0");
            //2.对msg重新进行序列化
            string body = msg->payload().SerializeAsString();
            //注意:如果跟原来的不一样大小返回false 不能影响到其他消息
            if(body.size() != msg->length())
            {
                 ERR_LOG("不能修改文件中的数据信息,因为新生成的数据与原数据长度不一致!");
                 return false;
            }
            //3.覆盖到文件中
            FileHelper helper(_datafile);
            bool ret = helper.write(body.c_str(),msg->offset(),body.size());
            if(ret == false)
            {
                ERR_LOG("向队列%s删除持久化消息失败!",_qname.c_str());
                return false;
            }
            return true;
         }
         //历史数据恢复/垃圾回收
         list gc()
         {
            list result;
            size_t msg_size =  0;//消息长度
            size_t offset = 0 ;//遍历文件提取有效消息的指针
            FileHelper helper(_datafile);
            std::size_t fsize = helper.size();
            //1.加载文件中的所有有效数据
            while(offset < fsize)
            {
                //1.1先读取4字节长度数据
                bool ret = helper.read((char*)&msg_size,offset,sizeof(size_t));
                if(ret == false)
                {
                    ERR_LOG("队列%s读取4字节长度失败!!",_qname.c_str());
                    return  list();
                }
                offset += sizeof(std::size_t);//往前移动size_t的长度字节
                std::size_t msg_offset = offset;
                //1.2再读取消息有效载荷
                string body(msg_size,'\0');
                ret = helper.read(&body[0],offset,msg_size);
                if(ret == false)
                {
                    ERR_LOG("队列%s读取消息失败!!",_qname.c_str());
                    return  result;
                }
                offset += msg_size; //往前偏移消息长度
                //1.3对消息的有效载荷反序列化
                MessagePtr msg = make_shared();
                msg->mutable_payload()->ParseFromString(body);
                //这里其实不能设置offset,因为这个offset是不准确的,毕竟前面可能有valid为1的消息
                //而且这里也用不着设置,因为后面写入临时文件的时候调用insert设置了
                // msg->set_length(msg_size);
                // msg->set_offset(msg_offset);
                //1.4根据valid标志位决定是否丢弃
                if(msg->payload().valid() == "0") continue;
                result.push_back(msg);
            }
            //2.将有效数据序列化存储到临时文件
            //注意这里要先创建出临时文件
            FileHelper::createFile(_tmpfile);
            for(auto& messsge : result)
            {
                bool n =  insert(_tmpfile,messsge);;
                if(n == false)
                {
                    ERR_LOG("向临时文件%s写入数据失败",_tmpfile.c_str());
                    return result;
                }
            }
            //3.删除源文件
            bool ret = FileHelper::removeFile(_datafile);
            if(ret == false)
            {
                ERR_LOG("删除源文件%s失败!",_datafile.c_str());
                return result;
            }
            //4.修改临时文件名为u源文件名称
            ret = FileHelper(_tmpfile).rename(_datafile);
            if(ret == false)
            {
                ERR_LOG("临时文件%s重命名失败!",_tmpfile.c_str());
                return result;
            }
            //5.返回新的有效数据
            return result;
        }
     private:
         bool insert(const string& filename,MessagePtr& msg)
         {
            //1.先对消息的有效载荷进行序列化获取格式化后的消息
            string body = msg->payload().SerializeAsString();
            //2.获取文件长度和消息长度
            FileHelper helper(filename);
            size_t fsize = helper.size();
            size_t msg_size = body.size();
            //3.先写入4字节长度 再写入消息
            bool ret = helper.write((char*)&msg_size,fsize,sizeof(size_t));
            if(ret == false)
            {
                ERR_LOG("队列%s新增消息写入消息长度失败!",_qname.c_str());
                return false;
            }
            ret = helper.write(body.c_str(),fsize+sizeof(std::size_t),msg_size);
            if(ret == false)
            {
                ERR_LOG("队列%s新增消息写入消息失败!",_qname.c_str());
                return false;
            }
            //4.更新messgaePtr中的实际存储位置
            msg->set_offset(fsize+sizeof(size_t));
            msg->set_length(msg_size);
            return true;
         }
     private:
         string _qname;
         string _datafile;
         string _tmpfile;
    };

队列单元消息管理

        如果内存中对所有的消息整体进行管理,则在进行垃圾回收以及恢复历史消息上就会变得麻烦。因此每个队列都有一个消息数据的管理结构,最终向外提供一个总体的消息管理类。

队列消息管理:

  1. 构造对象时:创建/打开队列数据文件,恢复队列历史消息数据
  2. 新增消息 / 确认消息(确认消息时如果消息是持久化过了的需要置标记位为1,同时确认是否进行垃圾回收)
  3. 垃圾回收:当持久化数据总量超过2000,且有效比例低于50%则进行垃圾回收
  4. 获取队首消息推送给客户端
  5. 删除队列所有消息
  6. 获取待推送消息数量
  7. 获取待确认消息数量
  8. 获取持久化消息数量

需要管理的数据:

  1. 持久化的管理句柄
  2. 待推送消息链表:以头插尾删的思想实现队列功能
  3. 持久化消息的哈希map:垃圾回收后需要更新消息数据(需要更新实际存储位置),用链表的话效率低,而且我们用的是智能指针,不管有几个链表几个哈希,它们保存的智能指针都指向同一个对象,一个地方修改其他地方也修改。
  4. 待确认消息的hashmap : 一条消息推送给客户端,并不会立即真正删除,而是等到被确认后才会删除。一条消息被推送到客户端后,取出待推送链表,加入到待确认结构中,等到确认后再删除。
  5. 持久化文件中有效消息数量
  6. 持久化文件中总体消息数量:和有效消息数量搭配可以计算出文件中有效消息比例(根据比例和总体数量决定是否进行垃圾回收)
  7. 队列名称
    ///队列单元消息管理
    class QueueMessage
    {
     public:
        using ptr = std::shared_ptr;
        QueueMessage( string& basedir,const string& qname)
        :_qname(qname),_valid_count(0),_total_count(0),
        _mapper(basedir,qname)
        {
        }
        bool recovery()
        {
            std::unique_lock lock(_mtx);
           //1.调用gc()来进行垃圾回收恢复历史消息
           _msgs = _mapper.gc();
           //2.将恢复后的消息插入到持久化map中
           for(auto& message : _msgs)
              _durable_msgs.insert(make_pair(message->payload().properties().id(), message));
           //3.更新valid_count 和 _total_count其实都等于恢复的消息数量
             _valid_count = _total_count = _msgs.size();
        }
        //向队列新增消息
        bool insert(const BasicProperties* bp,const string& body,bool is_queue_durable)
        {
            //1.根据传入的参数来构造消息对象
            MessagePtr msg = make_shared();
            msg->mutable_payload()->set_body(body);
            //1.1判断属性是否设置 没有我们需要手动设置属性
            if(bp != nullptr)
            {   //前提是队列允许持久化
               zmq::DeliveryMode mode = is_queue_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 //属性为空->队列不持久化消息也不持久化,没有意义
            {
               zmq::DeliveryMode mode = is_queue_durable ? zmq::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("");
            }
            //2.判断消息是否需要持久化
            std::unique_lock lock(_mtx);
            if(msg->payload().properties().delivery_mode() == zmq::DeliveryMode::DURABLE)
            {
                //2.1设置valid标志位为1
                msg->mutable_payload()->set_valid("1");
                //2.2使用持久化句柄进行持久化存储
               bool ret =  _mapper.insert(msg);
               if(ret == false)
               {
                    ERR_LOG("队列%s持久化消息失败",_qname.c_str());
                    return false;
               }
                //2.3更新持久化map和两个计数器
                _durable_msgs.insert(make_pair(msg->payload().properties().id(), msg));
                _valid_count++;
                _total_count++;
            }
            //3.将消息放到内存中进行管理
            _msgs.push_back(msg);
            // cout << "_msgs size:" << _msgs.size() << endl;
            return true;
        }
        //确认/删除消息:每次删除消息后,判断是否需要进行垃圾回收
        bool remove(const string& msg_id)
        {
            std::unique_lock lock(_mtx);
            //1.从待确认hashmap中查找消息
            auto it = _waitack_msgs.find(msg_id);
            if(it == _waitack_msgs.end())
            {
                DBG_LOG("未找到要删除的的消息:%s",msg_id.c_str());
                return true;
            }
            //2.判断该消息的模式是否为持久化
            if(it->second->payload().properties().delivery_mode() == zmq::DeliveryMode::DURABLE)
            {
                //2.1调用持久化句柄将消息删除
                _mapper.remove(it->second);
                //2.2 从durable_msgs中删除 并且更新valid_count
                _durable_msgs.erase(msg_id);
                _valid_count--;
                //2.3判断是否要垃圾回收,需要则垃圾回收更新消息存储位置和计数器
                gc();
            }
            //3.从内存中移除该消息
            _waitack_msgs.erase(msg_id);
        }
        //获取队首消息推送给客户端
        MessagePtr front()
        {
            unique_lock lock(_mtx);
            //1.先判断队内是否有消息
            if(_msgs.size() == 0)
            {
               DBG_LOG("队列%s目前没有消息可以消费",_qname.c_str());
               return MessagePtr();
            }
            //2.获取队首消息
            MessagePtr msg = _msgs.front();
            //注意这里不能使用引用来接收队首元素,因为这个智能指针被释放了pop_front,注意不是指向对象释放!!
            _msgs.pop_front();
            //3.向待确认hash表中添加一份,等待收到消息后再从hash表中删除
            _waitack_msgs.insert(make_pair(msg->payload().properties().id(), msg));
            return msg;
        }
        //删除队列所有消息
        void clear()
        {
            unique_lock lock(_mtx);
            _mapper.removeMsgFile();
            _msgs.clear();
            _durable_msgs.clear();
            _waitack_msgs.clear();
            _valid_count = _total_count = 0;
        }
        //获取待推送消息数量
        size_t getable_count()
        {
            unique_lock lock(_mtx);
            return _msgs.size();
        }
        //获取所有持久化消息数量
        size_t total_count()
        {
            unique_lock lock(_mtx);
            return _total_count;
        }
        //获取持久化消息数量
        size_t durable_count()
        {
            unique_lock lock(_mtx);
            return _durable_msgs.size();
        }
        //获取待确认消息数量
        size_t waitack_count()
        {
            unique_lock lock(_mtx);
            return _waitack_msgs.size();
        }
     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;
            //2.调用持久化句柄进行垃圾回收
            list result = _mapper.gc();
            //3.根据有效消息链表更新实际存储位置,没有在内存中添加管理的则重新添加管理
            for(auto& message : result)
            {
                auto it = _durable_msgs.find(message->payload().properties().id());
                if(it == _durable_msgs.end())
                {   //没有添加管理的重新推送至待推送链表&&加入duarable_msgs
                    DBG_LOG("垃圾回收后有一条消息没有持久化存储!");
                    _durable_msgs.insert(make_pair(message->payload().properties().id(),message));
                    _msgs.push_back(message);
                    continue;
                }
                //更新实际存储位置
               it->second->set_offset(message->offset());
               it->second->set_length(message->length());
            }
            //4.更新计数器
            _valid_count = _total_count = result.size();
        }
     private:
        std::mutex _mtx; //互斥锁保证两个map和待推送链表的线程安全
        string _qname;//队列名称
        size_t _valid_count;//持久化文件中有效消息数量
        size_t _total_count; //持久化文件中总体消息数量
        MessageMapper _mapper; //持久化的管理句柄
        list _msgs;//待推送消息
        std::unordered_map _durable_msgs;//持久化消息hash(方便垃圾回收更新消息实际存储位置)
        unordered_map _waitack_msgs;  //待确认消息hashmap(用于对消息确认,确认后删除消息)
    };
  • 消息持久化的前提是队列也持久化,如果属性设置并且队列也持久化,此时设置消息投递模式为传入的参数;如果属性没设置,则队列没持久化,消息就不持久化。
  • 持久化hash更新时机:1. 队列新增消息  2. 确认队列消息(如果该消息是持久化了的)
  • 待确认hash更新时机:1. 获取队首消息推送给客户端 2. 确认队列消息
  • 与durable_count不同的是,队列确定一条消息durable_count就会减少,而total_count不一定因为可能不会垃圾回收。

总体队列消息管理

管理成员:

  1. 每个队列的消息管理句柄:队列名称&队列消息管理句柄的hash表
  2. 互斥锁
  3. 所有消息文件存放目录

提供的操作:

  1. 创建队列的时候初始化队列的消息管理句柄:创建队列的时候调用
  2. 销毁队列的消息管理句柄:删除队列的时候调用
  3. 队列的各项消息操作:

(1)向队列新增消息

(2)获取队列队首消息

(3)对队列进行消息确认

(4)获取队列消息数量:可获取消息数量,持久化消息数量,待确认消息数量,总的持久化消息数量

(5)恢复队列历史消息

 //队列单元消息总体管理(队列--消息单元管理句柄)
    class MessageManager
    {
      public:
         using ptr = shared_ptr;
         MessageManager(const string& basedir)
         :_basedir(basedir)
         {}
         void clear()
         {
            unique_lock lock(_mtx);
            for(auto& message : _queue_msgs)
               message.second->clear();
         }
         //创建队列初始化队列消息管理句柄
         void initQueueMessage(const string& qname)
         {
            //1.查找消息管理句柄,有则返回
             QueueMessage::ptr qmp;
            {
                unique_lock lock(_mtx);
                auto it = _queue_msgs.find(qname);
                if(it != _queue_msgs.end()) return;
                qmp = make_shared(_basedir,qname);
                  //2.没有则构造 然后添加管理
               _queue_msgs.insert(make_pair(qname, qmp));
            }
           //恢复队列历史数据
           qmp->recovery();
        }
         //销毁队列消息管理句柄
         void destroyQueueMessage(const string& qname)
         {
            QueueMessage::ptr qmp;
            {
                 unique_lock lock(_mtx);
                //1.查找消息管理句柄
                auto it =_queue_msgs.find(qname);
                if(it == _queue_msgs.end()) return;
                //3.删除句柄
                qmp = it->second; //注意不能先删除迭代器再赋值
                _queue_msgs.erase(it);
            }
            //2.利用句柄删除数据
            qmp->clear();
         }
         //向指定队列新增消息
         bool insert(const string& qname,BasicProperties* bp,const string& body,bool is_queue_durable)
         {
            QueueMessage::ptr qmp;
            {
                 unique_lock lock(_mtx);
                 //1.查找消息管理句柄
                 auto it =_queue_msgs.find(qname);
                 if(it == _queue_msgs.end())
                 {
                     ERR_LOG("没有找到队列%s的消息管理句柄,新增消息失败!",qname.c_str());
                     return false;
                 }
                 qmp = it->second;
            }
            //2.新增
            return qmp->insert(bp, body,is_queue_durable);
         }
         //向指定队列获取队首消息
         MessagePtr front(const string& qname)
         {
            QueueMessage::ptr qmp;
            {
                 unique_lock lock(_mtx);
                 //1.查找消息管理句柄
                 auto it =_queue_msgs.find(qname);
                 if(it == _queue_msgs.end())
                 {
                     ERR_LOG("没有找到队列%s的消息管理句柄,获取队首消息失败!",qname.c_str());
                     return MessagePtr();
                 }
                 qmp = it->second;
            }
            //调用句柄的front;
            return qmp->front();
         }
         //对指定队列进行消息确认
         void ack(const string& qname,const string& msg_id)
         {
            QueueMessage::ptr qmp;
            {
                 unique_lock lock(_mtx);
                 //1.查找消息管理句柄
                 auto it =_queue_msgs.find(qname);
                 if(it == _queue_msgs.end())
                 {
                     ERR_LOG("没有找到队列%s的消息管理句柄,对消息%s确认失败!",qname.c_str(),msg_id.c_str());
                     return;
                 }
                 qmp = it->second;
            }
            qmp->remove(msg_id);
         }
         //获得指定队列的待推送消息数量
         size_t getable_count(const string& qname)
         {
            QueueMessage::ptr qmp;
            {
                 unique_lock lock(_mtx);
                 //1.查找消息管理句柄
                 auto it =_queue_msgs.find(qname);
                 if(it == _queue_msgs.end())
                 {
                     ERR_LOG("没有找到队列%s的消息管理句柄,获取getable_count失败",qname.c_str());
                     return 0;
                 }
                 qmp = it->second;
            }
            return qmp->getable_count();
         }
           //获得指定队列持久化文件中总的持久化消息数量
           //(与durable_count不同的是,队列确定一条消息durable_count就会减少,
           // 而total_count不一定因为可能不会垃圾回收)
         size_t total_count(const string& qname)
         {
             QueueMessage::ptr qmp;
            {
                 unique_lock lock(_mtx);
                 //1.查找消息管理句柄
                 auto it =_queue_msgs.find(qname);
                 if(it == _queue_msgs.end())
                 {
                     ERR_LOG("没有找到队列%s的消息管理句柄,获取total_count失败",qname.c_str());
                     return 0;
                 }
                 qmp = it->second;
            }
            return qmp->total_count();
         }
          //获得指定队列的持久化消息数量
         size_t durable_count(const string& qname)
         {
            QueueMessage::ptr qmp;
            {
                 unique_lock lock(_mtx);
                 //1.查找消息管理句柄
                 auto it =_queue_msgs.find(qname);
                 if(it == _queue_msgs.end())
                 {
                     ERR_LOG("没有找到队列%s的消息管理句柄,获取durable_count失败",qname.c_str());
                     return 0;
                 }
                 qmp = it->second;
            }
            return qmp->durable_count();
         }
          //获得指定队列的待推送消息数量
         size_t waitack_count(const string& qname)
         {
            QueueMessage::ptr qmp;
            {
                 unique_lock lock(_mtx);
                 //1.查找消息管理句柄
                 auto it =_queue_msgs.find(qname);
                 if(it == _queue_msgs.end())
                 {
                     ERR_LOG("没有找到队列%s的消息管理句柄,获取waitack__count失败",qname.c_str());
                     return 0;
                 }
                 qmp = it->second;
            }
            return qmp->waitack_count();
         }
      private:
        std::mutex _mtx;
        string _basedir;
        std::unordered_map _queue_msgs;//队列名称-队列消息管理句柄
    };
  • 服务器启动的时候我们需要恢复历史消息,那我们就需要知道存在哪些队列,之前的MsgQueueManager类提供了从数据库获取所有队列信息的接口,能让我们知道所有的队列名称,但是这样做模块之间会有耦合,因此我们在整合虚拟机的时候再根据队列信息进行初始化队列。

initQueueMessage():

        //创建队列初始化队列消息管理句柄
         void initQueueMessage(const string& qname)
         {
            //1.查找消息管理句柄,有则返回
            {
                unique_lock lock(_mtx);
                auto it = _queue_msgs.find(qname);
                if(it != _queue_msgs.end()) return;
            }
            //2.没有则构造 然后添加管理
            QueueMessage::ptr qmp = make_shared(_basedir,qname);
            {
               unique_lock lock(_mtx);
               _queue_msgs.insert(make_pair(qname, qmp));
            }
        }

原本初始化队列消息管理,我们是先查找句柄再添加管理,但是这样做添加管理这两个操作不是原子性的(有可能查找的时候里面已经插入了),因为qmp构造的时候是需要恢复历史消息的,这个过程是比较耗时的,因此解决方案是将qmp构造函数里的历史消息恢复封装成一个单独的接口,等添加管理后再调用进行恢复历史消息:

    class QueueMessage
    {
     public:
        using ptr = std::shared_ptr;
        QueueMessage( string& basedir,const string& qname)
        :_qname(qname),_mapper(basedir,qname)
        {
        }
        bool recovery()
        {
            std::unique_lock lock(_mtx);
           //1.调用gc()来进行垃圾回收恢复历史消息
           _msgs = _mapper.gc();
           //2.将恢复后的消息插入到持久化map中
           for(auto& message : _msgs)
              _durable_msgs.insert(make_pair(message->payload().properties().id(), message));
           //3.更新valid_count 和 _total_count其实都等于恢复的消息数量
             _valid_count = _total_count = _msgs.size();
        }
         //创建队列初始化队列消息管理句柄
         void initQueueMessage(const string& qname)
         {
            //1.查找消息管理句柄,有则返回
             QueueMessage::ptr qmp;
            {
                unique_lock lock(_mtx);
                auto it = _queue_msgs.find(qname);
                if(it != _queue_msgs.end()) return;
                qmp = make_shared();
                  //2.没有则构造 然后添加管理
               _queue_msgs.insert(make_pair(qname, qmp));
            }
           //恢复队列历史数据
           qmp->recovery();
        }

虚拟机管理

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

1. 定义虚拟机类包含以下成员:

        (1) 交换机数据管理模块句柄

        (2) 队列数据管理模块句柄

        (3) 绑定数据管理模块句柄

        (4) 消息数据管理模块句柄

        (5) 虚拟机名称

2. 虚拟机包含操作 :

        (1) 提供声明交换机的功能 (存在则OK否则创建)

        (2) 提供删除交换机的功能 (删除交换机的同时删除相关的绑定信息)

        (3) 提供声明队列的功能(存在则OK不存在则创建,创建的同时创建队列关联消息管理对象)

        (4) 提供删除队列的功能 (删除队列的同时删除关联绑定信息,删除关联消息管理对象以及队列所有消息)

        (5) 提供交换机-队列绑定的功能

        (6) 提供交换机-队列解绑的功能

        (7) 提供获取交换机相关的所有绑定信息功能

        (8) 提供新增消息的功能

        (9) 提供获取指定队列队首消息的功能

        (10) 提供消息确认删除删除的功能

3. 虚拟机管理操作 : 虚拟机的增删查

整个实现思想其实就是要实现什么服务,就利用相关句柄完成即可:

class VirtualHost
  {
    public:
      using ptr = shared_ptr;
      VirtualHost(const string& host_name,const string& basedir,const string& dbfile)
      :_host_name(host_name),
      _emp(make_shared(dbfile))
      ,_mqmp(make_shared(dbfile))
      ,_bmp(make_shared(dbfile))
      ,_mmp(make_shared(basedir))
      {
         //获取所有队列信息
         QueueMap queues = _mqmp->allQueues();
         //恢复队列消息
         for(auto& queue : queues)
          _mmp->initQueueMessage(queue.first);
      }
      //声明交换机
      void declareExchange(const string& ename,ExchangeType etype,bool edurable,bool eauto_delete
                          ,const google::protobuf::Map&eargs)
      {
          return _emp->declareExchange(ename,  etype, edurable, eauto_delete,eargs);
      }
      //删除交换机
      void deleteExchange(const string& ename)
      {
            //删除交换机的时候删除相关的绑定信息
            _bmp->removeExchangeBindings(ename);
            return _emp->deleteExchange(ename);
      }
      //声明队列
      bool declareQueue(const string& qname,bool qdurable,bool qexclusive,bool qauto_delete,
                        const google::protobuf::Map& qargs)
      {
          //1.初始化队列消息管理句柄
          _mmp->initQueueMessage(qname);
          //2.队列创建
          return _mqmp->declareQueue(qname,qdurable,qexclusive,qauto_delete,qargs);
      }
      //删除队列
      void deleteQueue(const string& qname)
      {
          //1.删除队列消息
          _mmp->destroyQueueMessage(qname);
          //2.删除队列相关绑定信息
          _bmp->removeMsgQueueBindings(qname);
          //3.删除队列
          _mqmp->deleteQueue(qname);
      }
      //绑定队列-交换机
      bool bind(const string& ename,const string& qname,const string& key)
      {
         //1.交换机是否存在
         Exchange::ptr exchange =_emp->selectExchange(ename);
         if(exchange == nullptr)
         {
             ERR_LOG("交换机%s不存在无法绑定!",ename.c_str());
             return false;
         }
         //2.队列是否存在
         MsgQueue::ptr msg = _mqmp->selectQueue(qname);
         if(msg == nullptr)
         {
             ERR_LOG("队列%s不存在无法绑定!",qname.c_str());
             return false;
         }
         //3.绑定
         return _bmp->bind(ename, qname, key, exchange->_durable & msg->_durable);
      }
      //解绑队列-交换机
      void unBind(const string& ename,const string& qname)
      {
         return _bmp->unBind(ename,qname);
      }
      bool existExchange(const string& ename)
      {
         return _emp->exists(ename);
      }
      bool existMsgQueue(const string& qname)
      {
           return _mqmp->exists(qname);
      }
      bool existBinding(const string& ename,const string& qname)
      {
          return _bmp->exists(ename,qname);
      }
      //获取指定交换机的绑定信息
      MsgQueueBindMap exchangeBindings(const string& ename)
      {
          return _bmp->getExchangeBindings(ename);
      }
      //在指定队列发布一条消息
      bool publish(const string& qname,BasicProperties* bp,const string& body)
      {
         //1.获取指定队列持久化信息
         MsgQueue::ptr queue = _mqmp->selectQueue(qname);
         if(queue.get() == nullptr)
         {
             ERR_LOG("指定队列%s不存在,发布消息失败!",qname.c_str());
             return false;
         }
         //2.发布消息
         return _mmp->insert(qname, bp, body,queue->_durable);
      }
      //在指定队列队首取出一条消息
      MessagePtr consume(const string& qname)
      {
          return _mmp->front(qname);
      }
      //在指定队列确实/删除一条消息
      void ack(const string& qname,const string& msg_id)
      {
         return _mmp->ack(qname,msg_id);
      }
      void clear()
      {
            _emp->clear();
            _mqmp->clear();
            _bmp->clear();
            _mmp->clear();
      }
      Exchange::ptr selectExchange(const string& ename)
      {
         return _emp->selectExchange(ename);
      }
      MsgQueue::ptr selectMsgQueue(const string& qname)
      {
         return _mqmp->selectQueue(qname);
      }
      QueueMap allQueues()
      {
         return _mqmp->allQueues();
      }
    private:
      string _host_name;
      ExchangeManager::ptr _emp; //交换机数据管理句柄
      MsgQueueManager::ptr _mqmp; //队列数据管理句柄
      BindingManager::ptr _bmp;  //绑定信息数据管理句柄
      MessageManager::ptr _mmp;  //消息数据管理句柄
  };

交换路由模块

客户端将消息发布到指定的交换机,交换机这时候要考虑这条数据该放入到哪些与自己绑定的队列中,而这个考量是通过交换机类型以及匹配规则来决定的:

  1. 广播交换:直接将消息交给所有绑定的队列,无需匹配。
  2. 直接交换:队列绑定信息中的binding_key与消息中的routing_key一致则匹配成功,否则失败。
  3. 主题交换:只有匹配队列主题的消息才会被放入队列中。

binding_key:是由数字字母下划线构成的,并且使用.分成若干部分,并支持*#通配符。例:news.music.#两种通配符,但是*#只能作为.切分出来的独立部分,不能和其他数字字母混用。

  • 支持*#两种通配符,但是*#只能作为切分出来的独立部分,不能和其他数字字母混用
  • 比如a.*.b是合法的,a.*a.b是不合法的。
  • *可以匹配任意一个单词(注意是单词不是字母)
  • #可以匹配任意零个或多个单词。(注意是单词不是字母)

注意事项:

(1)一个单词中不能既出现*又出现#,也就是,一个单词中只能有一个通配符,且必须独立存在

(2)*#通配符不能连续出现,因为#可以匹配任意多个任意单词,因此连续出现是没有意义的(*和*可以连续出现)。


routing_key :是由数据、字母和下划线组成,并且可以使用.划分成若干部分。例如 news.music.pop,这用于表示当前发布的消息是一个流行音乐的新闻。

取决要素:

  1. 交换机类型
  2. routing_keybinding_key的匹配

基于功能需求分析,路由交换模块只需要对传入的数据进行处理即可,因此这个模块要实现实际上是功能接口类,没有成员变量。因此提供的功能有:

  1. 判断routing_key是否合法:a-z, A-Z, 0-9, ._组成
  2. 判断binding_key是否合法:必须是a-z, A-Z, 0-9, *, #, ._组成
  3. 进行routing_keybinding_key路由匹配

    class Router
    {
     public:
       //判断传入的的routing_key是否合法
       static bool isLegalRoutingKey(const string& routing_key)
       {
          for(const auto& ch : routing_key)
          {
                if(ch >= 'a' && ch <= 'z') continue;
                if(ch >= 'A' && ch <= 'Z') continue;
                if(ch >= '0' && ch <= '9') continue;
                if(ch == '.' || ch == '_') continue;
                return false;
          }
          return true;
       }
       //判断传入的binding_key是否合法
       static bool isLegalBindingKey(const string& binding_key)
       {
          //1.判断是否含有非法字符
            for(const auto& ch : binding_key)
            {
                    if(ch >= 'a' && ch <= 'z') continue;
                    if(ch >= 'A' && ch <= 'Z') continue;
                    if(ch >= '0' && ch <= '9') continue;
                    if(ch == '.' || ch == '_' || ch == '*' || ch == '#') continue;
                    return false;
            }
            //2.判断两个通配符是否独立存在(即判断一个单词的长度是否大于1且含有*或#)
            vector bind_strs;
            StringHelper::SplitStr(binding_key,".", bind_strs);
            for(auto& str : bind_strs)
            {   //news.a#.bb
                if(str.size()>1 && (str.find('#')!=string::npos || str.find('*')!=string::npos))
                   return false;
            }
            //3.判断两个通配符是否连续出现
            for(int i = 1 ;  i < bind_strs.size() ; i++)
            {
                if(bind_strs[i] == "#" && bind_strs[i-1] == "*") return false;
                else if(bind_strs[i] == "#" && bind_strs[i-1] == "#") return false;
                else if(bind_strs[i] == "*" && bind_strs[i-1] == "#") return false;
            }
            return true;
       }
       //路由:根据交换机类型和匹配规则进行路由,即消息应该放到哪队列
       static bool route(zmq::ExchangeType type,const string& routing_key,const string& binding_key)
       {
            //1.DIRECT:直接返回binding_key和routing_key是否相同
            if(type == ExchangeType::DIRECT) return binding_key == routing_key;
            //2.FANOUT:返回true --- 广播
            if(type == ExchangeType::FANOUT) return true;
            //3.主题交换:动态规划
            vector bind_strs;
            vector route_strs;
            StringHelper::SplitStr(binding_key,".", bind_strs);
            StringHelper::SplitStr(routing_key, ".", route_strs);
            int n = bind_strs.size();
            int m = route_strs.size();
            //3.1状态表示:dp[i][j]表示str1的前i个单词能否和str2的前i个单词匹配成功
            vector> dp(n+1,vector(m+1,false));
            //3.2初始化
            dp[0][0] = true;
            for(int i = 1 ; i <= n ; i++)
            {
                if(bind_strs[i-1] != "#") break;
                dp[i][0] = true;
            }
            //3.3状态转移&&填表
            for(int i = 1 ; i <= n ; i++)
             for(int j = 1 ; j <= m ; j++)
             {
                if(bind_strs[i-1] == route_strs[j-1] || bind_strs[i-1] == "*")
                    dp[i][j] = dp[i-1][j-1];
                else if(bind_strs[i-1] == "#")
                    dp[i][j] = dp[i-1][j] | dp[i-1][j-1] | dp[i][j-1];
             }
             return dp[n][m];
       }
    };

主题交换涉及的动态规划算法思路:

消费者管理

客户端这边每当发起一个订阅请求,意味着服务器这边就多了一个订阅者(处理消息的客户端描述),而这个消费者或者说订阅者它是和队列直接关联的,因为订阅请求中会描述当前用户想要订阅哪一个队列的消息。而一个信道关闭的时候,或者队列被删除的时候,那么这个信道或队列关联的消费者也就没有存在的意义了,因此也需要将相关的消费者信息删除掉。基于以上需求,因此需要对订阅者信息进行管理。

注意:我们是队列有了消息之后才需要找到消费者,消费者更多是队列收到消息的时候被查找。而不是信道被关闭的时候,因此消费者的管理是基于队列而不是信道。

消费者信息类

管理要素:

a. 消费者标识

b. 订阅的队列名称

c. 一个消息的处理回调函数(实现的是当发布一条消息到队列,则选择消费者进行消费,如何消费?对于服务端来说就是调用这个回调函数进行处理,其内部逻辑是找到消费者对应的连接,然后将数据发送给消费者对应的客户端

  • 回调函数类型:void(const string& ctag,const BasicProperties&, const string&body)

d. 是否自动应答标志。(一个消息被消费者消费后,若自动应答,则直接移除待确认消息,否则等待客户端确认

    using ConsumerCallback = std::function;
    //消费者类
    struct Consumer
    {
        using ptr = shared_ptr;
        string _tag; //消费者标识
        string _qname; //消费者订阅的队列名称
        bool _auto_ack;//自动确认标志
        ConsumerCallback _callback;
        Consumer(){}
        Consumer(const string& ctag,const string& qname,
                 bool auto_ack,const ConsumerCallback& cb)
        :_tag(ctag),_qname(qname),_auto_ack(auto_ack),_callback(cb)
        {}
    };

注意:消费者是和连接信道相关的,信道关闭就不需要再给他推送消息,因此持久化消费者是没有意义的。

以队列为单元的消费者管理

管理的数据:

(1)消费者管理结构:vector(不用哈希表是为了数组方便RR轮转)

(2)轮转序号:一个队列可能会有多个消费者,但是一条消息只需要被一个消费者消费即可,因此采用RR轮转。

(3)互斥锁:保证线程安全。

(4)队列名称

提供的操作:

(1) 新增消费者 : 信道提供的服务是订阅队列消息的时候创建。

(2) 删除消费者:取消订阅 / 信道关闭 / 连接关闭 的时候删除。

(3) 获取消费者:从队列所有的消费者中按序取出一个消费者进行消息的推送。

(4) 判断队列消费者是否为空

(5) 判断指定消费者是否存在

(6) 清理队列所有消费者

 //以队列为单元的消费者管理结构
    class QueueConsumer
    {
      public:
        using ptr = shared_ptr;
        QueueConsumer(const string& qname)
        :_qname(qname),_rr_seq(0)
        {}
        //队列新增消费者
        Consumer::ptr create(const string& ctag,const string& qname,
                 bool auto_ack,const ConsumerCallback& cb)
        {
            unique_lock lock(_mtx);
            //1.先从数组中查找是否存在
            for(auto& consumer : _consumers)
            {
                if(consumer->_tag == ctag) //找到直接返回
                return consumer;
            }
            //2.没找到则构建对象
            auto consumer = make_shared(ctag,qname,auto_ack,cb);
            //3.添加管理
            _consumers.push_back(consumer);
            return consumer;
        }
        //队列移除消费者
        void remove(const string& ctag)
        {
            unique_lock lock(_mtx);
            //1.查找是否已经移除
            auto it = _consumers.begin();
            for( ; it != _consumers.end() ; it++)
            {
                if((*it)->_tag == ctag)
                {
                    _consumers.erase(it);
                    return;
                }
            }
        }
        //队列获取消费者:RR轮转获取
        Consumer::ptr chooseConsumer()
        {
            unique_lock lock(_mtx);
            //1.注意可能没有消费者
            if(_consumers.size() == 0)
            {
                ERR_LOG("当前队列%s没有消费者!",_qname.c_str());
                return Consumer::ptr();
            }
            //2.增加序号
            uint64_t num = (_rr_seq++) % _consumers.size();
            return _consumers[num];
        }
        //是否为空
        bool empty()
        {
            unique_lock lock(_mtx);
            return (_consumers.size() == 0);
        }
        //判断指定消费者是否存在
        bool exists(const string& ctag)
        {
            unique_lock lock(_mtx);
            //1.查找是否已经移除
            auto it = _consumers.begin();
            for( ; it != _consumers.end() ; it++)
            {
                if((*it)->_tag == ctag)
                    return true;
            }
            return false;
        }
        //清理所有消费者
        ///消费者跟连接通道相关,通道关闭,
        // 就不需要再给他推送消息,因此持久化消费者是没有意义的
        void clear()
        {
            unique_lock lock(_mtx);
            _rr_seq = 0;
            _consumers.clear();
        }
      private:
        string _qname;
        std::mutex _mtx;
        uint64_t _rr_seq;//RR轮转序号
        vector _consumers;
    };

队列消费者总体管理

管理操作:

a. 初始化队列的消费者信息结构(创建队列的时候初始化)

b. 向指定队列新增消费者(客户端订阅指定队列消息的时候)

c. 从指定队列移除消费者(客户端取消订阅的时候)

d. 移除指定队列的所有消费者(队列被删除时销毁)

e. 从指定队列获取一个消费者(轮询获取-消费者轮换消费起到负载均衡的作用)

f. 判断队列中消费者是否为空

g. 判断队列中指定消费者是否存在

h. 清理所有消费者

   class ConsumerManager
    {
     public:
         using ptr = shared_ptr;
         ConsumerManager()
         {}
         //初始化队列单元消费者管理句柄
         void initQueueConsumer(const string& qname)
         {
            unique_lock lock(_mtx);
            //1.先查找指定句柄是否存在
            auto it = _qconsumers.find(qname);
            if(it != _qconsumers.end())
               return;
            //2.没找到则进行构造
            QueueConsumer::ptr qcp = make_shared(qname);
            //3.添加管理
            _qconsumers[qname] = qcp;
         }
         //销毁指定队列的单元消费者管理句柄
         void destroyQueueConsumer(const string& qname)
         {
            unique_lock lock(_mtx);
            //1.先查找指定句柄是否存在
            auto it = _qconsumers.find(qname);
            if(it == _qconsumers.end())
               return;
            //2.移除管理,析构之后清理消费者
            _qconsumers.erase(qname);
         }
         //向指定队列添加消费者
         Consumer::ptr create(const string& ctag,const string& qname,
                 bool auto_ack,const ConsumerCallback& cb)
         {
            QueueConsumer::ptr qmp;
            {
                unique_lock lock(_mtx);
                //1.先查找指定句柄是否存在
                auto it = _qconsumers.find(qname);
                if(it == _qconsumers.end())
                {
                    ERR_LOG("队列%s不存在,添加消费者失败!",qname.c_str());
                    return Consumer::ptr();
                }
                qmp = it->second;
            }
            //2.找到则调用句柄进行添加
           return qmp->create(ctag, qname,auto_ack,cb);
         }
         //移除指定队列的指定消费者
         void remove(const string& ctag,const string& qname)
         {
            QueueConsumer::ptr qmp;
            {
                unique_lock lock(_mtx);
                //1.先查找指定句柄是否存在
                auto it = _qconsumers.find(qname);
                if(it == _qconsumers.end())
                {
                    ERR_LOG("队列%s不存在,删除消费者失败!",qname.c_str());
                    return;
                }
                qmp = it->second;
            }
            //2.找到则调用句柄进行添加
            return qmp->remove(ctag);
         }
         //选择指定队列的消费者进行消费
         Consumer::ptr chooseConsumer(const string& qname)
         {
            QueueConsumer::ptr qmp;
            {
                unique_lock lock(_mtx);
                //1.先查找指定句柄是否存在
                auto it = _qconsumers.find(qname);
                if(it == _qconsumers.end())
                {
                    ERR_LOG("队列%s不存在,选择消费者失败!",qname.c_str());
                    return Consumer::ptr();
                }
                qmp = it->second;
            }
            //2.选择消费者
            return qmp->chooseConsumer();
         }
         //判断指定队列的消费者是否为空
         bool empty(const string& qname)
         {
            QueueConsumer::ptr qmp;
            {
                unique_lock lock(_mtx);
                //1.先查找指定句柄是否存在
                auto it = _qconsumers.find(qname);
                if(it == _qconsumers.end())
                {
                    ERR_LOG("队列%s不存在,判断消费者是否为空失败!",qname.c_str());
                    return false;
                }
                qmp = it->second;
            }
            //2.选择消费者
            return qmp->empty();
         }
         //判断指定队列的消费者是否存在
         bool exists(const string& ctag,const string& qname)
         {
               QueueConsumer::ptr qmp;
               {
                    unique_lock lock(_mtx);
                    //1.先查找指定句柄是否存在
                    auto it = _qconsumers.find(qname);
                    if(it == _qconsumers.end())
                    {
                        ERR_LOG("队列%s不存在判断指定队列的消费者是否存在失败!",qname.c_str());
                        return false;
                    }
                    qmp = it->second;
               }
                //2.选择消费者
                return qmp->exists(ctag);
         }
         //清除所有消费者
         void clear()
         {
            unique_lock lock(_mtx);
            _qconsumers.clear();
         }
      private:
         std::mutex _mtx;
         unordered_map _qconsumers;
    };

信道管理模块

   在AMQP模型中,除了通信连接Connection概念外,还有一个Channel的概念,Channel是针对Connection连接的一个更细粒度的通信通道,多个Channel可以使用同一个通信连接Connection进行通信,但是同一个ConnectionChannel之间相互独立,而信道模块就是再次将上述模块进行整合提供服务的模块。

网络通信协议设计

        生产者和消费者都是客户端,它们都需要通过网络和Broker Server进行通信。具体通信的过程我们使用Muduo库来实现,使用TCP作为为通信的底层协议,同时在这个基础上自定义应用层协议,完成客户端对服务器功能的的远端调用。我们要实现的远端调用接口包括:

  • 创建channel
  • 关闭channel
  • 创建exchange
  • 删除exchange
  • 创建queue
  • 删除queue
  • 创建binding
  • 删除binding
  • 发送message
  • 订阅message
  • 发送ack
  • 返回message(服务器->客户端)
syntax="proto3";
package zmq;
import "mq_msg.proto";
//信道的打开和关闭
message openChannelRequest {
    string rid = 1;//请求id
    string cid = 2;//信道id
};
message closeChannelRequest {
    string rid = 1;
    string cid = 2;
};
//交换机的声明与删除
message declareExchangeRequest{
    string rid = 1;
    string cid = 2;
    string exchange_name = 3; //交换机名称
    ExchangeType exchange_type = 4;//交换机类型
    bool durable = 5; //持久化标志
    bool auto_delete = 6; //自动删除标志
    map args = 7;
};
message deleteExchangeRequest {
    string rid = 1;
    string cid = 2;
    string exchange_name = 3; //交换机名称
};
//队列声明与删除
message declareQueueRequest {
   string rid = 1;
   string cid = 2;
   string queue_name = 3;
   bool exclusive = 4;
   bool durable = 5;
   bool auto_delete = 6;
   map args = 7;
};
message deleteQueueRequest {
    string rid = 1;
    string cid = 2;
    string queue_name = 3;
};
//队列的绑定与解绑
message queueBindRequest {
    string rid = 1;
    string cid = 2;
    string exchange_name = 3;
    string queue_name = 4;
    string binding_key = 5;
};
message queueUnBindRequest {
    string rid = 1;
    string cid = 2;
    string exchange_name = 3;
    string queue_name = 4;
};
//消息的发布
message basicPublishRequest {
    string rid = 1;
    string cid = 2;
    string exchange_name = 3; //发布到哪个交换机
    string body = 4;
    BasicProperties properties = 5;
};
//消息的确认
message basicAckRequest {
    string rid = 1;
    string cid = 2;
    string queue_name = 3;
    string message_id = 4;
};
//队列的订阅
message basicConsumRequest{
    string rid = 1;
    string cid = 2;
    string consumer_tag = 3;//消费者标识
    string queue_name = 4;
    bool auto_ack = 5;
};
//订阅的取消
message basicCancelRequest{
    string rid = 1;
    string cid = 2;
    string consumer_tag = 3;//消费者标识
    string queue_name = 4;
};
//消息的推送
message  basicConsumeRequest {
    string cid = 1;
    string consumer_tag = 2;
    string body = 3;
    BasicProperties properties = 4;
};
//通用响应
message basicCommonResponse {
    string rid = 1;
    string cid = 2;
    bool ok = 3;
};

信道模块

1. 管理信息:

(1)信道ID:信道的唯一标识。

(2)信道关联的消费者:用于消费者信道在关闭的时候取消订阅,删除订阅消息

(3)信道关联的连接:用于向客户端发送数据(响应,推送的消息)

(4)protobuf协议处理句柄:网络通信前的协议处理

(5)消费者管理句柄 : 信道关闭/取消订阅的时候,通过句柄删除订阅者信息

(6)虚拟机句柄 : 交换机 / 队列 / 绑定 / 消息数据管理

(7)工作线程池句柄 (一条消息被发布到队列后 , 需要将消息推送给订阅了对应队列的消费者, 过程由线程池完成)

2. 管理操作

(1)提供声明&删除交换机操作(删除交换机的同时删除交换机关联的绑定信息

(2)提供声明&删除队列操作(删除队列的同时,删除队列关联的绑定信息,消息,消费者信息

(3)提供绑定&解绑队列操作

(4)提供订阅&取消订阅队列消息操作

(5)提供发布&确认消息操作

    class Channel
    {
      public:
        using ptr = shared_ptr;
        Channel(const string& id,const VirtualHost::ptr& host,const ConsumerManager::ptr& cmp,
               const ProtobufCodecPtr& codec,const muduo::net::TcpConnectionPtr& conn,
               const ThreadPool::ptr& pool)
         :_cid(id),_conn(conn),_codec(codec),_cmp(cmp),_host(host),_pool(pool)
        {}
        ~Channel()
        {
            if(_consumer.get() != NULL)
            {
               //移除消费者信息取消订阅
               _cmp->remove(_consumer->_tag,_consumer->_qname);
            }
        }
        //交换机的声明与删除
        void declareExchange(const declareExchangeRequestPtr& req)
        {
          //1.声明交换机
          _host->declareExchange(req->exchange_name(),req->exchange_type(),req->durable(),
                                             req->auto_delete(),req->args());
          //2.组织响应发送给客户端
          basicResponse(req->rid(),req->cid(), true);
        }
        void deleteExchange(const deleteExchangeRequestPtr& req)
        {
            _host->deleteExchange(req->exchange_name());
            return basicResponse(req->rid(), req->cid(), true);
        }
        //队列的声明与删除
        void declareQueue(const declareQueueRequestPtr& req)
        {
            //1.声明队列
            bool ret = _host->declareQueue(req->queue_name(),req->durable(),req->exclusive(),
                                          req->auto_delete(),req->args());
            if(ret == false)
            {
                DBG_LOG("声明队列失败!");
                return basicResponse(req->rid(), req->cid(), false);
            }
            //2.初始化队列消费者
            _cmp->initQueueConsumer(req->queue_name());
            return basicResponse(req->rid(), req->cid(), true);
        }
        void deleteQueue(const deleteQueueRequestPtr& req)
        {
             //1.虚拟机句柄
             _host->deleteQueue(req->queue_name());
             //2.删除队列消费者结构
             _cmp->destroyQueueConsumer(req->queue_name());
             //3.组织响应
             return basicResponse(req->rid(), req->cid(), true);
        }
        //队列的绑定与解除绑定
        void bind(const queueBindRequestPtr& req)
        {
            _host->bind(req->exchange_name(), req->queue_name(), req->binding_key());
            return basicResponse(req->rid(), req->cid(), true);
        }
        void unBind(const queueUnBindRequestPtr& req)
        {
             _host->unBind(req->exchange_name(),req->queue_name());
             return basicResponse(req->rid(), req->cid(), true);
        }
        //消息的发布
        void publish(const basicPublishRequestPtr& req)
        {
              //1.判断交换机是否存在
              auto ep = _host->selectExchange(req->exchange_name());
              if(ep.get() == nullptr) return basicResponse(req->rid(),req->cid(),false);
              //2.进行交换路由判断消息可以发布到交换机的哪个队列中
              MsgQueueBindMap mqbp = _host->exchangeBindings(req->exchange_name());
              zmq::BasicProperties* properties = nullptr;
              string routing_key;
              //2.1判断是否设置了属性
              if(req->has_properties())
              {
                  properties = req->mutable_properties();
                  routing_key = req->mutable_properties()->routing_key();
              }
              for(auto& binding : mqbp)
              {
                  if(Router::route(ep->_type, routing_key, binding.second->_binding_key))
                  {
                      //3.路由匹配则添加消息到队列中(添加消息的管理)
                      _host->publish(binding.first , properties, req->body());
                      //4.向线程池中添加有一个消费任务(向指定队列消费者推送消息)
                      auto task = std::bind(&Channel::messageConsume,this,binding.first);
                      _pool->push(task);
                  }
              }
             return basicResponse(req->rid(), req->cid(), true);
        }
        //消息确认
        void ack(const basicAckRequestPtr& req)
        {
           _host->ack(req->queue_name(), req->message_id());
           return basicResponse(req->rid(), req->cid(), true);
        }
        //订阅队列消息
        void consume(const basicConsumeRequestPtr & req)
        {
            //1.判断队列是否存在
            bool ret = _host->existMsgQueue(req->queue_name());
            if(ret == false)
            {
                ERR_LOG("订阅队列消息失败,队列%s不存在",req->queue_name().c_str());
                return;
            }
            //2.创建队列的消费者
            auto cb = std::bind(&Channel::callback,this,std::placeholders::_1,std::placeholders::_2
                                     ,std::placeholders::_3);
             _consumer = _cmp->create(req->consumer_tag(), req->queue_name(),req->auto_ack(),cb);
             return basicResponse(req->rid(),_cid, true);
        }
        //取消订阅
        void cancel(const basicCancelRequestPtr& req)
        {
            //1.移除队列消费者结构
            _cmp->remove(req->consumer_tag(), req->queue_name());
            //2.组织响应
            return basicResponse(req->rid(), req->cid(), true);
        }
      private:
         //让线程池中线程向指定队列消费者推送消息
         void messageConsume(const string& qname)
         {
             //1.从队列中取出一条消息
             MessagePtr msg = _host->consume(qname);
             if(msg.get() == nullptr)
             {
                 ERR_LOG("向队列%s消费者推送消息失败,队列中没有消息!",qname.c_str());
                 return;
             }
             //2.从队列订阅者中取出一个订阅者
             Consumer::ptr cp = _cmp->chooseConsumer(qname);
             if(cp.get() == nullptr)
             {
                 ERR_LOG("向队列%s消费者推送消息失败,队列中没有消费者!",qname.c_str());
                 return;
             }
             //3.使用订阅者对应的消息处理函数实现消息的推送
             ///参数:消费者标识 属性 正文
             cp->_callback(cp->_tag,msg->mutable_payload()->mutable_properties(),msg->payload().body());
             //4.判断如果订阅者是自动确认 则不需要等待确认直接删除消息,否则需要外部收到消息确认后再删除
             if(cp->_auto_ack) _host->ack(qname,msg->mutable_payload()->properties().id());
         }
         void basicResponse(const string& rid,const string&cid,bool ok)
         {
            //1.构造basicCommonResponse
            zmq::basicCommonResponse resp;
            resp.set_rid(rid);
            resp.set_cid(cid);
            resp.set_ok(ok);
            //2.序列化后发送
            _codec->send(_conn,resp);
         }
         //消费者处理回调
         void callback(const string& ctag,const BasicProperties* bp,const string& body)
         {
             //根据参数组织出推送消息请求 将消息推送给channel对应客户端
             basicConsumeResponse resp;
             resp.set_consumer_tag(ctag);
             resp.set_cid(_cid);
             resp.set_body(body);
             //判断属性是否为空
             if(bp != nullptr)
             {
                resp.mutable_properties()->set_id(bp->id());
                resp.mutable_properties()->set_delivery_mode(bp->delivery_mode());
                resp.mutable_properties()->set_routing_key(bp->routing_key());
             }
             _codec->send(_conn,resp);
         }
      private:
        string _cid; //信道id
        Consumer::ptr _consumer;//信关联的消费者
        muduo::net::TcpConnectionPtr _conn;
        ProtobufCodecPtr _codec;
        ConsumerManager::ptr _cmp;
        VirtualHost::ptr _host;
        ThreadPool:: ptr _pool;
    };

连接管理模块

        该模块向用户提供一个用于实现网络通信的Connection对象,从其内部可创建出粒度更轻的Channel对象,用于与客户端进行网络通信。

连接设计

1. 成员信息:

(1)连接关联的信道管理句柄(实现信道的增删查)

(2)连接关联的实际用于通信的muduo::net::Connection

(3)protobuf协议处理的句柄(ProtobufCodeC对象)

(4)消费者管理句柄

(5)虚拟机句柄

(6)异步工作线程池

后面五个成员其实是提供给创建信道所需要的资源

2. 连接操作

(1)提供创建Channel信道的操作

(2)提供删除Channel信道的操作

class Connection
   {
     public:
          using ptr = shared_ptr;
          Connection(const muduo::net::TcpConnectionPtr& conn,const ProtobufCodecPtr& codec,const ConsumerManager::ptr& cmp,
                    const VirtualHost::ptr& host,const ThreadPool::ptr& pool)
            :_conn(conn)
            ,_codec(codec)
            ,_cmp(cmp)
            ,_host(host)
            ,_pool(pool)
            ,_channels(make_shared())
          {}
          ~Connection()
          {}
          void openChannel(const openChannelRequestPtr& req)
          {
             //1.判断信道id是否重复,创建信道
             bool ret = _channels->openChannel(req->cid(),_host, _cmp, _codec, _conn,_pool);
             if(ret == false)
             {
                 ERR_LOG("创建的信道%s已经存在,信道id重复!",req->cid().c_str());
                 return basicResponse(req->rid(),req->cid(), false);
             }
             DBG_LOG("信道创建成功!");
             //2.给客户端进行回复
            return basicResponse(req->rid(),req->cid(), true);
          }
          void closeChannel(const closeChannelRequestPtr& req)
          {
              _channels->closeChannel(req->cid());
              return basicResponse(req->rid(), req->cid(), true);
          }
          Channel::ptr getChannel(const string& channel_id)
          {
              return _channels->getChannel(channel_id);
          }
     private:
          void basicResponse(const string& rid,const string& cid,bool ok)
          {
                basicCommonResponse resp;
                resp.set_rid(rid);
                resp.set_cid(cid);
                resp.set_ok(ok);
                _codec->send(_conn,resp);
          }
     private:
          muduo::net::TcpConnectionPtr _conn;
          ProtobufCodecPtr _codec;
          ConsumerManager::ptr _cmp;
          VirtualHost::ptr _host;
          ThreadPool::ptr _pool;
          ChannelManager::ptr _channels;
   };

连接的管理

class ConnectionManager
   {
     public:
        using ptr = shared_ptr;
        ConnectionManager()
        {}
        void newConnection(const muduo::net::TcpConnectionPtr& conn,const ProtobufCodecPtr& codec,const ConsumerManager::ptr& cmp,
                    const VirtualHost::ptr& host,const ThreadPool::ptr& pool)
        {
            unique_lock lock(_mtx);
            auto it = _conns.find(conn);
            if(it != _conns.end()) return;
            Connection::ptr self_conn = make_shared(conn,codec,cmp,host,pool);
            _conns.insert(make_pair(conn, self_conn));
        }
        void delConnection(const muduo::net::TcpConnectionPtr& conn)
        {
            unique_lock lock(_mtx);
            auto it = _conns.find(conn);
            if(it == _conns.end()) return;
            _conns.erase(it);
        }
        Connection::ptr getConnection(const muduo::net::TcpConnectionPtr& conn)
        {
            unique_lock lock(_mtx);
            auto it = _conns.find(conn);
            if(it == _conns.end()) return Connection::ptr();
            return it->second;
        }
     private:
        std::mutex _mtx;
        unordered_map _conns;
   };

服务器模块

构建Muduo服务器需要资源:

  • _server:Muduo库提供的一个通用TCP服务器,我们可以封装这个服服务器进行TCP通信。
  • _baseloop:主事件循环器,用于响应IO事件和定时器事件,主loop主要是为了响应监听描述符的IO事件
  • _codec:一个protobuf编解码器,我们在TCP服务器上设计了一层应用层协议,这个编码器主要是负责实现应用层协议的解析和封装。
  • _dispatcher:一个消息分发器,当Socket接收到一个报文消息后,我们需要按照消息的类型,即上面提到的typeName进行消息分发,会将不同类型的消息分发到相对应的处理函数中。

服务器提供服务需要的资源:

  • _consumer_manager:服务器中的消费者信息管理句柄
  • _threadpool:异步工作线程池,主要用于队列消息的推送工作
  • _connection_manager:连接管理句柄,管理当前服务器上的所有已经建立的通信连接。
  • _virtual_host:服务器持有的虚拟主机。队列、交换机、绑定、消息等数据都是通过虚拟主机管理。

服务器提供服务主要思想就是获取相应的连接对象,然后根据连接获取信道提供服务,在这之前我们需要给每个服务注册回调:

    #define HOST_NAME "host1" //目前支持单个虚拟机
    #define DBFILE "./meta.db" //持久化使用的数据库文件
    class Server
    {
    public:
        typedef std::shared_ptr MessagePtr; //这里不要忘了
        Server(int port,const string& basedir)
        :_server(&_baseloop,InetAddress("0.0.0.0",port),"proServer",muduo::net::TcpServer::kReusePort)
        ,_dispatcher(std::bind(&Server::onUnknownMessage,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3))
        ,_codec(std::make_shared(std::bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher,
                    std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)))
        ,_host(make_shared(HOST_NAME,basedir,basedir+DBFILE))
        ,_consumer_manager(make_shared())
        ,_connection_manager(make_shared())
        ,_pool(make_shared())
        {
            //1.为提供的服务注册回调
            _dispatcher.registerMessageCallback(std::bind(&Server::onOpenChannel,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onCloseChannel,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onDeclareExchange,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onDeleteExchange,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onDeclareQueue,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onDeleteQueue,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onQueueBind,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onQueueUnbind,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onBasicPublish,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onBasicAck,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onBasicConsume,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            _dispatcher.registerMessageCallback(std::bind(&Server::onBasicCancel,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //2.设置连接建立和收到消息的回调
            _server.setConnectionCallback(std::bind(&Server::onConnection,this,std::placeholders::_1));
            _server.setMessageCallback(std::bind(&ProtobufCodec::onMessage,_codec.get(),std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
            //3.初始化历史消息中的队列消费者结构
            QueueMap allQueues = _host->allQueues();
            for(auto& queue : allQueues)
                _consumer_manager->initQueueConsumer(queue.first);
        }
    public:
        void Start()
        {
            _server.start();
            _baseloop.loop();//开启事件监控
        }
        //打开信道
        void onOpenChannel(const muduo::net::TcpConnectionPtr& conn,const openChannelRequestPtr& message,muduo::Timestamp)
        {
            //1.查找连接对象
            Connection::ptr connection = _connection_manager->getConnection(conn);
            if(connection.get() == nullptr)
            {
                ERR_LOG("没有找到对应连接对象!");
                conn->shutdown();
                return;
            }
            //2.通过连接对象创建信道
            return connection->openChannel(message);
        }
        //关闭信道
        void onCloseChannel(const muduo::net::TcpConnectionPtr& conn,const closeChannelRequestPtr& message,muduo::Timestamp)
        {
                //1.查找连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.关闭信道
                connection->closeChannel(message);
        }
        //声明交换机
        void onDeclareExchange(const muduo::net::TcpConnectionPtr& conn,const declareExchangeRequestPtr& message,muduo::Timestamp)
        {
                //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法声明交换机",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->declareExchange(message);
        }
        //删除交换机
        void onDeleteExchange(const muduo::net::TcpConnectionPtr& conn,const deleteExchangeRequestPtr& message,muduo::Timestamp)
        {
                //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法删除交换机",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->deleteExchange(message);
        }
        //声明队列
        void onDeclareQueue(const muduo::net::TcpConnectionPtr& conn,const declareQueueRequestPtr& message,muduo::Timestamp)
        {
               //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法声明队列",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->declareQueue(message);
        }
        //删除队列
        void onDeleteQueue(const muduo::net::TcpConnectionPtr& conn,const deleteQueueRequestPtr& message,muduo::Timestamp)
        {
               //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法删除队列",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->deleteQueue(message);
        }
        //队列绑定
        void onQueueBind(const muduo::net::TcpConnectionPtr& conn,const queueBindRequestPtr& message,muduo::Timestamp)
        {
               //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法进行队列绑定",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->bind(message);
        }
        //队列解绑
        void onQueueUnbind(const muduo::net::TcpConnectionPtr& conn,const queueUnBindRequestPtr& message,muduo::Timestamp)
        {
               //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法队列解绑",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->unBind(message);
        }
        //消息发布
        void onBasicPublish(const muduo::net::TcpConnectionPtr& conn,const basicPublishRequestPtr& message,muduo::Timestamp)
        {
            //    DBG_LOG("有客户端发布消息...");
               //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法消息发布",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->publish(message);
        }
        //消息确认
        void onBasicAck(const muduo::net::TcpConnectionPtr& conn,const basicAckRequestPtr& message,muduo::Timestamp)
        {
               //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法消息确认",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->ack(message);
        }
        //队列消息订阅
        void onBasicConsume(const muduo::net::TcpConnectionPtr& conn,const basicConsumeRequestPtr& message,muduo::Timestamp)
        {
               //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s无法订阅消息",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->consume(message);
        }
        //队列消息取消订阅
        void onBasicCancel(const muduo::net::TcpConnectionPtr& conn,const basicCancelRequestPtr& message,muduo::Timestamp)
        {
               //1.获取连接对象
                Connection::ptr connection = _connection_manager->getConnection(conn);
                if(connection.get() == nullptr)
                {
                    ERR_LOG("没有找到对应连接对象!");
                    conn->shutdown();
                    return;
                }
                //2.通过连接对象来获取信道
                auto channel = connection->getChannel(message->cid());
                if(channel.get() == nullptr)
                {
                    ERR_LOG("没有对应信道%s,无法取消订阅队列消息",message->cid().c_str());
                    return;
                }
                //3.使用信道提供的服务
                return channel->cancel(message);
        }
        void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn,const MessagePtr& message,muduo::Timestamp)
        {
            LOG_INFO << "onUnknownMessage:" << message->GetTypeName();
            //关闭连接
            conn->shutdown();
        }
        void onConnection(const muduo::net::TcpConnectionPtr& conn)
        {
            if(conn->connected())
            {
                 LOG_INFO << "连接获取成功:" << conn->peerAddress().toIpPort();
                 _connection_manager->newConnection(conn,_codec,_consumer_manager,_host,_pool);
            }
            else
            {
                 LOG_INFO << "连接建立失败" ;
                 _connection_manager->delConnection(conn);
            }
        }
    private:
        muduo::net::EventLoop _baseloop;
        muduo::net::TcpServer _server;  //服务器对象
        ProtobufDispatcher _dispatcher; //请求分发器 --- 要向其中注册请求处理函数
        ProtobufCodecPtr _codec;//protobuf协议处理器 --- 针对收到的请求数据进行protobuf协议处理
        VirtualHost::ptr _host;
        ConsumerManager::ptr _consumer_manager;//消费者管理句柄
        ConnectionManager::ptr _connection_manager; //连接管理句柄
        ThreadPool::ptr _pool;
    };

posted @ 2025-12-02 09:09  yangykaifa  阅读(16)  评论(0)    收藏  举报