一个泛型(消息类型M)的消息队列实现
1. 消息队列的模式
1)简单模式:当客户端(生产者)将消息写入到消息队列中时,消息队列中消息的数量加1,消费者实时监听消息队列,当队列中有消息时,
则获取消息,之后执行业务逻辑,同时消息队列的消息数量减1。
特点:一个生产者P发送消息到队列Q,一个消费者C接收。
2)工作模式:由一个生产者负责消息写入队列,但是如果只有一个消费者负责消费,可能会造成消息的积压,所以准备多个消费者共同消费
一个队列中的消息。
特点:一个生产者,多个消费者,每个消费者获取到的消息唯一,多个消费者只有一个队列。
3)发布/订阅模式(Publish/Subscribe):一个生产者发送的消息会被多个消费者获取,每个消费者得到的消息是一样的,这点不同于工作模式。
topic:含义是服务端存放消息的容器
比如微信公众号,不同的人订阅同一个公众号,收到的消息都是一样的。
发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息。topic实现了发布和订阅,当你发布一个消息,
所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到这个消息的拷贝。
2. 发布订阅模式的消息队列具体实现
一个topic(可认为成公众号)只产生一种类型的消息,topicName(公众号名字)用来标识公众号,具有唯一性。订阅某一个公众号的所有订阅者们
都具有相同的topicName,每个topic包含一个成员变量set来存储它们的全部订阅者。
先贴一张整体的框架图,后续进行逐个模块分析:
1)消息处理函数的类型抽象:当消息队列要处理消息的时候,只需请求基于这个抽象的类型所定义的对象,而无需关心具体的实现。
M:消息类型参数化。 T:消息处理类类型参数化。
很明显,想使用该类型,程序员需要提供一个类型M和消息处理类(T)。M可以是int, string,json等,当然也可以是一个类类型。
为什么还要定义 MsgHandle 这么一个抽象接口呢?这样做的话,定义基本类型的变量时可以更简洁(只有一个参数M),抽象程度更高。
即形如 MsgHandle<M> *handle,而不是 MsgHandle<T,M> *handle。
template <typename M> class MsgHandle { public: MsgHandle() {} virtual ~MsgHandle() {} public: virtual void Func(const M &) = 0; }; template <typename T, typename M> class MsgHandleInterface : public MsgHandle<M> { typedef void (T::*Handle)(const M &); // 指向类 T 中一个函数的指针 public: MsgHandleInterface(Handle fp, T *obj) : _handle(fp), _obj(obj) {} ~MsgHandleInterface() {} public: void Func(const M &msg) // 请求该类型对象中的Func函数进行消息处理 { (_obj->*_handle)(msg); } private: Handle _handle; T *_obj; };
2)消息处理线程的类型抽象:一个消息处理线程监听一个消息队列,不断从消息队列中取出消息到临时队列,然后调用传递进来的
消息处理类的成员函数进行处理。
template <typename M> class MsgHandleThread { public: MsgHandleThread(MsgHandle<M> *handle) : _newMsg(false), _handle(handle), _stopped(true), _stop(false) {} ~MsgHandleThread() { Stop(); if(_handle != nullptr) delete _handle; _handle = nullptr; } public: void Start() { if(_stopped == false) return; _stop = _stopped = false; _runHandle = std::thread(&MsgHandleThread::_Run, this); } void Stop() { std::unique_lock<std::mutex> msgLock(_msgMutex); _newMsg = _stop = true; _msgCondt.notify_all(); _runHandle.join(); } bool addMsg(const M &msg) { std::unique_lock<std::mutex> msgLock(_msgMutex); _msgQueue.push(new M(msg)); _newMsg = true; _msgCondt.notify_all(); return true; } private: void _Run() { std::queue<M*> readyQueue; while(true) { std::unique_lock<std::mutex> msgLock(_msgMutex); if(!_newMsg) _msgCondt.wait(msgLock); // wait for push _newMsg = false; if(_stop == true) break; while(!_msgQueue.empty()) // 将消息队列里的所有消息pop的准备队列中 { readyQueue.push(_msgQueue.front()); _msgQueue.pop(); } msgLock.unlock(); while(!readyQueue.empty()) { M *msg = readyQueue.front(); readyQueue.pop(); if(msg == NULL) continue; _handle->func(*msg); delete msg; } } _stopped = true; } private: bool _stop, _stopped; bool _newMsg; std::mutex _msgMutex; std::condition_variable _msgCondt; std::thread _runHandle; std::queue<M*> _msgQueue; MsgHandle<M> *_handle; };
3)消费者类型抽象:以微信公众号为例,每个订阅者(Subscriber)需要知道自己订阅了什么公众号(topicName)。有消息提醒时就去浏览信息(消息处理线程)。
template <typename M> class Subscriber { public: Subscriber(const std::string &topicName, MsgHandle<M> *handle) : _topicName(topicName) , _handleThread(new MsgHandleThread<M>(handle)) // 抽象接口只能创建指针 { _handleThread->Start(); } ~Subscriber() { if(_handleThread != nullptr) delete _handleThread; _handleThread = nullptr; } bool addMsg(const M &msg) { return _handleThread->addMsg(msg); } const std::string& GetTopicName() const { return _topicName; } private: std::string _topicName; MsgHandleThread<M> *_handleThread; };
4)生产者类型抽象:比如微信的公众号(topic)就是一个生产消息的生产者,用 topicName 来表示一个公众号,一个公众号只可以产生一种类型的消息,
可以有很多个人(Subscriber)进行订阅,成员变量包含一个set来存储所有的订阅者。当有消息产生的时候,每个订阅者者都会收到相同的消息。
template<typename M> class Topic { typename std::set<Subscriber<M>*>::iterator iter; public: Topic(const std::string &topicName) : _topicName(topicName) {} ~Topic() { _mtx.lock(); for(iter = _subscribers.begin(); iter != _subscribers.end(); ++iter) delete *iter; _mtx.unlock(); } public: Subscriber<M>* AddSubscriber(MsgHandle<M> *handle) { Subscriber<M> *subscriber = new Subscriber<M>(_topicName, handle); if(subscriber != nullptr) { _mtx.lock(); _subscribers.insert(subscriber); _mtx.unlock(); } return subscriber; } Subscriber<M>* AddSubscriber(Subscriber<M> *subscriber) { if(subscriber != nullptr && subscriber->GetTopicName() == _topicName) { _mtx.lock(); _subscribers.insert(subscriber); _mtx.unlock(); } return subscriber; } bool DelSubcriber(Subscriber<M> *subscriber) { if(subscriber != nullptr && _subscribers.find(subscriber) != _subscribers.end()) { _mtx.lock(); _subscribers.erase(subscriber); delete subscriber; _mtx.unlock(); } return true; } bool PushMsg(const M &msg) { _mtx.lock(); for(iter = _subscribers.begin(); iter != _subscribers.end(); ++iter) (*iter)->addMsg(msg); // 每个订阅者都会收到相同的消息 _mtx.unlock(); return true; } const std::string& GetTopicName() const { return _topicName; } private: std::string _topicName; std::set<Subscriber<M>*> _subscribers; // 订阅相同消息的订阅者 std::mutex _mtx; };
贴一张1-4类之间的关系图:
5)订阅流程类型抽象:消费者可以通过请求这个对象订阅某个公众号,生产者通过请求这个对象进行消息发布和订阅者管理。
类型M和topicName是一一对应的,一个topicName只会产生一种M的消息。
class Communicate { typedef typename std::set<Subscriber<M>*>::iterator sIter; typedef typename std::vector<Topic<M>*>::iterator vIter; public: // 创建新的公众号 template <typename M> static bool CreateTopic(const std::string &topicName) { Communicate *instance = GetInstance(); bool ret = false; if(topicName.size() == 0) return false; // invalid topic name instance->_busMutex.lock(); if(instance->_topicsNames.find(topicName) != instance->_topicsNames.end()) goto _return; // topic already exist std::vector<Topic<M>*> *topics = static_cast<std::vector<Topic<M>*>*>(instance->_topicsIndex[typeid(M)]); if(topics == nullptr) // 如果该类型的第二级索引还未建立,就创建一个vector { topics = new std::vector<Topic<M>*>; instance->_topicsIndex[typeid(M)] = topics; } Topic<M> *topic = new Topic<M>(topicName); // 建立一个新的公众号对象 if(instance->_topicSubcribes.find(topicName) != instance->_topicSubcribes.end()) { std::set<Subscriber<M>*> *subscribers = static_cast<std::set<Subscriber<M>*>*>(instance->_topicSubcribes[topicName]); for(sIter = subscribers->begin(); sIter != subscribers->end(); ++sIter) topic->AddSubscriber(*sIter); // 将最初就订阅该topic的订阅者加入集合 } topics->push_back(topic); instance->_topicsNames.insert(topicName); // 注册 ret = true; _return: instance->_busMutex.unlock(); return ret; } template<typename T, typename M> static Subscriber<M>* Subscribe(const std::string &topicName, void (T::*fp)(const M &), T *obj) { Communicate *instance = GetInstance(); Subscriber<M> *subscriber = nullptr; if(topicName.size() == 0) return nullptr; // invalid topic name instance->_busMutex.lock(); if(instance->_topicsNames.find(topicName) == instance->_topicsNames.end()) // 该公众号系统还未注册 { MsgHandle<M> *handle = new MsgHandleInterface<T, M>(fp, obj); subscriber = new Subscriber<M>(topicName, handle); goto _return; } if(instance->_topicsIndex.find(typeid(M)) == instance->_topicsIndex.end()) goto _return; // topic type error std::vector<Topic<M>*> *topics = static_cast<std::vector<Topic<M>*>*>(instance->_topicsIndex[typeid(M)]); if(topics == nullptr) goto _return; // topic type error for(vIter = topics->begin(); vIter != topics->end(); ++vIter) { if((*vIter)->GetTopicName() == topicName) // 第二级索引按 topicName 查找 { MsgHandle<M> *handle = new MsgHandleInterface<T, M>(fp, obj); subscriber = (*vIter)->AddSubscriber(handle); // 找到对应公众号后加入订阅者 break; } } _return: if(subscriber != nullptr) { std::set<Subscriber<M>*> *subscribers = nullptr; if(instance->_topicSubcribes.find(topicName) == instance->_topicSubcribes.end()) { subscribers = new std::set<Subscriber<M>*>; instance->_topicSubcribes[topicName] = subscribers; } subscribers = static_cast<std::set<Subscriber<M>*>*>(instance->_topicSubcribes[topicName]); subscribers->insert(subscriber); // 因为公众号还不存在,所以订阅者就先暂存在 _topicSubcribes,后面等产生了 topic 在进行拷贝 } instance->_busMutex.unlock(); return subscriber; } template<typename M> static bool UnSubscribe(Subscriber<M> *subscriber) { Communicate *instance = GetInstance(); bool ret = false; if(subscriber == nullptr) return false; std::string topicName = subscriber->GetTopicName(); if(topicName.size() == 0) return false; // invalid topic name instance->_busMutex.lock(); if(instance->_topicsNames.find(topicName) == instance->_topicsNames.end()) goto _return; // topic already exist if(instance->_topicsIndex.find(typeid(M)) == instance->_topicsIndex.end()) goto _return; // topic type error std::vector<Topic<M>*> *topics = static_cast<std::vector<Topic<M>*>*>(instance->_topicsIndex[typeid(M)]); if(topics == nullptr) goto _return; // topic type error for(vIter = topics->begin(); vIter != topics->end(); ++vIter) { if((*vIter)->GetTopicName() == topicName) // 第二级索引按 topicName 查找 { (*vIter)->DelSubcriber(subscriber); // 找到对应公众号后删除该订阅者 subscriber = nullptr; ret = true; break; } } _return: instance->_busMutex.unlock(); return ret; } template<typename M> static bool Publish(const std::string &topicName, const M &msg) { Communicate *instance = GetInstance(); bool ret = false; if(topicName.size() == 0) return false; // invalid topic name instance->_busMutex.lock(); if(instance->_topicsNames.find(topicName) == instance->_topicsNames.end()) goto _return; // topic already exist if(instance->_topicsIndex.find(typeid(M)) == instance->_topicsIndex.end()) goto _return; // topic type error std::vector<Topic<M>*> *topics = static_cast<std::vector<Topic<M>*>*>(instance->_topicsIndex[typeid(M)]); if(topics == nullptr) goto _return; // topic type error for(vIter = topics->begin(); vIter != topics->end(); ++vIter) { if((*vIter)->GetTopicName() == topicName) // 第二级索引按 topicName 查找 { ret = (*vIter)->PushMsg(msg); // 找到该公众号后,发布消息,触发每个订阅者去处理 break; } } _return: instance->_busMutex.unlock(); return ret; } private: Communicate() {} static Communicate* GetInstance() { static Communicate instance; return &instance; } /* 1. _topicsIndex 和 _topicsNames 是已经存在的公众号,即系统已经注册了 topic。 2. _topicsIndex 采用两级索引进行公众号查找,速度比直接按 topicName 查找来的快 3. _topicSubcribes 中只是用户想订阅该公众号,即对应的 topic 不一定存在,当用户想订阅 某公众号,但系统还没注册,就先暂存在 _topicSubcribes。 */ std::unordered_map<std::type_index, void*> _topicsIndex; // map of std::pair<std::type_index, std::vector<Topic<M>*>*> std::unordered_map<std::string, void* > _topicSubcribes; // 直接通过 topicName 直接得到该公众号所有订阅者。 如果有订阅者想要订阅 // 某个公众号,但系统还没有注册该公众号时,这里也会往对应的set插入一 // 个subscriber对象,即产生一个后台处理线程,当有人CreateTopic之后 // 再把这里保存的订阅者添加到topic内。 std::set<std::string> _topicsNames; // 注册的公众号 std::mutex _busMutex; };
如何使用这个消息队列呢?
/* 创建公众号。 会实例化出 MsgHandle<std::string> MsgHandleThread<std::string> Subscriber(std::string) Topic(std::string) 然后产生类型为 std::string 的二级索引,如果 vector 已存在就不创建了 产生一个名为 name 的 topic 加入vector */ Communicate::CreateTopic<std::string>("name"); /* 订阅 name 公众号,类T为自己实现的消息处理类 然后将 _nameSubcriber 放入公众号 name 中的set集合 */ Subscriber<std::string> *_nameSubcriber = Communicate::Subscribe("name", &T::func, this); /* 任意位置调用公众号发布消息的接口就可以触发订阅者的处理程序 */ Communicate::Publish("name", "My name is Jack."); /* 不想触发对应的处理,就直接解除订阅即可 */ Communicate::UnSubscribe(_nameSubcriber);