订阅者模块设计与实现
一、模块概述
订阅者模块是消息队列消费层的基础实体单元,用于封装消费者身份、订阅关系、消费模式与回调接口。作为消费者管理器与信道模块的底层支撑,该模块采用轻量化结构体设计,仅负责属性存储与生命周期管理,职责单一、对接简洁。
二、核心功能
- 定义消费者唯一标识、订阅队列、自动确认模式;
- 封装消息到达后的异步回调接口;
- 提供构造/析构日志,便于追踪对象生命周期;
- 统一使用智能指针托管,实现资源自动释放。
三、核心结构
tag:消费者唯一标签qname:订阅的目标队列auto_ack:是否自动消息确认callback:消息推送回调函数
四、模块代码
cpp
#ifndef __M_CONSUMER_H__
#define __M_CONSUMER_H__
#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_msg.pb.h"
namespace Fy_mq{
using ConsumerCallback = std::function<void(const std::string &, const BasicProperties *bp, const std::string &)>;
struct Consumer {
using ptr = std::shared_ptr<Consumer>;
std::string tag;
std::string qname;
bool auto_ack;
ConsumerCallback callback;
Consumer(){ DLOG("new Consumer: %p",this); }
Consumer(const std::string &ctag,const std::string &queue_name,bool ack_flag,const ConsumerCallback &cb):
tag(ctag),qname(queue_name),auto_ack(ack_flag),callback(std::move(cb)){
DLOG("new Consumer: %p",this);
}
~Consumer(){ DLOG("del Consumer: %p", this); }
};
};
#endif
信道管理模块设计与实现
一、模块概述
客户端信道管理模块是自研消息队列客户端核心通信组件,负责客户端与服务端的会话建立、业务请求交互、消息接收回调及信道生命周期管控。模块基于Muduo网络库与Protobuf协议,采用Channel+ChannelManager双层架构,封装了消息队列全业务请求接口,实现请求响应同步等待、线程安全调度,为客户端提供简洁、可靠的通信支撑,是客户端与服务端交互的核心桥梁。
二、核心功能
- 信道生命周期管理:自动生成唯一信道ID,支持信道创建、打开、关闭,析构时自动取消订阅,避免资源泄漏;
- 全业务请求封装:提供交换机/队列声明、删除、绑定、解绑,消息发布、确认,消费者订阅、取消订阅等全接口;
- 同步响应机制:通过条件变量与互斥锁,实现请求发送后同步等待服务端响应,保证交互可靠性;
- 消息消费回调:接收服务端推送消息,通过绑定的消费者回调函数,将消息分发至业务层处理;
- 全局信道管控:通过ChannelManager实现多信道的统一创建、删除、查询,保证多线程并发安全。
三、核心架构设计
1. 模块结构
采用"单信道会话+全局管理器"双层设计:
- Channel:单个通信会话载体,封装TCP连接、协议编解码器,维护消费者对象与请求响应缓存,处理具体业务请求;
- ChannelManager:全局信道管理器,基于互斥锁+哈希表,实现所有信道的线程安全管理,提供创建、删除、查询接口。
2. 核心成员与依赖
- 核心成员:信道唯一ID(_cid)、TCP连接指针、Protobuf编解码器指针、消费者对象、互斥锁、条件变量、响应缓存哈希表;
- 依赖模块:Muduo网络库(TCP连接、编解码)、Protobuf协议(请求/响应封装)、消费者模块(Consumer)、公共工具(日志、UUID生成)。
四、核心接口实现
1. 信道基础操作
- 信道创建:自动生成唯一CID,绑定TCP连接与编解码器;
- 打开/关闭信道:向服务端发送对应请求,同步等待响应,完成会话建立与释放;
- 信道标识获取:提供cid()接口,返回当前信道唯一ID,用于全局定位。
2. 业务请求接口
封装消息队列全业务请求,统一设置请求ID(rid)与信道ID(cid),确保请求与响应一一对应:
- 交换机管理:declareExchange(声明)、deleteExchange(删除);
- 队列管理:declareQueue(声明)、deleteQueue(删除)、QueueBind(绑定)、QueueUnBind(解绑);
- 消息操作:basicPublish(发布消息)、basicAck(消息确认);
- 消费者操作:basicConsumer(订阅消息)、basicCancel(取消订阅)。
3. 响应与消息处理
- 响应缓存:通过putBasicResponse接口接收服务端响应,存入哈希表并唤醒等待线程;
- 同步等待:waitResponse接口通过条件变量阻塞,等待对应请求的响应,接收后自动清理缓存;
- 消息推送:consume接口接收服务端推送消息,校验消费者标识后,通过回调函数分发至业务层。
4. 全局信道管理
ChannelManager提供简洁的信道管控接口,所有操作加互斥锁,保证多线程安全:
- create:创建信道并加入全局管理;
- remove:根据CID删除信道,释放资源;
- get:根据CID查询信道,用于请求分发与消息处理。
五、模块代码
cpp
#ifndef __M_CHANNEL_H__
#define __M_CHANNEL_H__
#include "muduo/net/TcpConnection.h"
#include "muduo/proto/codec.h"
#include "muduo/proto/dispatcher.h"
#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_msg.pb.h"
#include "../mqcommon/mq_proto.pb.h"
#include "mq_consumer.hpp"
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <unordered_map>
namespace Fy_mq{
using ProtobufCodecPtr = std::shared_ptr<ProtobufCodec>;
using basicConsumeResponsePtr = std::shared_ptr<basicConsumeResponse>;
using basicCommonResponsePtr = std::shared_ptr<basicCommonResponse>;
class Channel{
public:
using ptr = std::shared_ptr<Channel>;
Channel(const muduo::net::TcpConnectionPtr& conn,const ProtobufCodecPtr &codec):
_cid(UUIDHelper::uuid()),_conn(conn),_codec(codec){}
~Channel(){
basicCancel();
}
std::string cid(){
return _cid;
}
bool openChannel(){
std::string rid = UUIDHelper::uuid();
openChannelRequest req;
req.set_rid(rid);
req.set_cid(_cid);
_codec->send(_conn,req);
basicCommonResponsePtr resp = waitResponse(rid);
return resp->ok();
}
void closeChannel(){
std::string rid = UUIDHelper::uuid();
closeChannelRequest req;
req.set_rid(rid);
req.set_cid(_cid);
_codec->send(_conn,req);
waitResponse(rid);
}
bool declareExchange(
const std::string &name,
ExchangeType type,
bool durable,
bool auto_delete,
google::protobuf::Map<std::string,std::string> &args){
std::string rid = UUIDHelper::uuid();
declareExchangeRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_exchange_name(name);
req.set_exchange_type(type);
req.set_durable(durable);
req.set_auto_delete(auto_delete);
req.mutable_args()->swap(args);
_codec->send(_conn, req);
basicCommonResponsePtr resp = waitResponse(rid);
return resp->ok();
}
void deleteExchange(const std::string &name){
std::string rid = UUIDHelper::uuid();
deleteExchangeRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_exchange_name(name);
_codec->send(_conn,req);
waitResponse(rid);
}
bool declareQueue(
const std::string &qname,
bool qdurable,
bool qexclusive,
bool qauto_delete,
google::protobuf::Map<std::string,std::string> &qargs){
std::string rid = UUIDHelper::uuid();
declareQueueRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_queue_name(qname);
req.set_durable(qdurable);
req.set_exclusive(qexclusive);
req.set_auto_delete(qauto_delete);
req.mutable_args()->swap(qargs);
_codec->send(_conn, req);
basicCommonResponsePtr resp = waitResponse(rid);
return resp->ok();
}
void deleteQueue(const std::string &qname){
std::string rid = UUIDHelper::uuid();
deleteQueueRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_queue_name(qname);
_codec->send(_conn,req);
waitResponse(rid);
}
bool QueueBind(
const std::string &ename,
const std::string &qname,
const std::string &key){
std::string rid = UUIDHelper::uuid();
queueBindRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_exchange_name(ename);
req.set_queue_name(qname);
req.set_binding_key(key);
_codec->send(_conn, req);
basicCommonResponsePtr resp = waitResponse(rid);
return resp->ok();
}
void QueueUnBind(const std::string &ename,const std::string &qname){
std::string rid = UUIDHelper::uuid();
queueUnBindRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_exchange_name(ename);
req.set_queue_name(qname);
_codec->send(_conn,req);
waitResponse(rid);
}
void basicPublish(const std::string &ename,const BasicProperties *bp,const std::string &body){
std::string rid = UUIDHelper::uuid();
basicPublishRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_exchange_name(ename);
req.set_body(body);
if(bp != nullptr){
req.mutable_properties()->set_id(bp->id());
req.mutable_properties()->set_deliver_mode(bp->deliver_mode());
req.mutable_properties()->set_routing_key(bp->routing_key());
}
_codec->send(_conn,req);
waitResponse(rid);
}
void basicAck(const std::string &msgid){
if(_consumer.get() == nullptr){
DLOG("消息确认时,找不到消费者信息!");
return;
}
std::string rid = UUIDHelper::uuid();
basicAckRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_queue_name(_consumer->qname);
req.set_message_id(msgid);
_codec->send(_conn,req);
waitResponse(rid);
}
void basicCancel(){
if(_consumer.get() == nullptr){
return;
}
std::string rid = UUIDHelper::uuid();
basicCancelRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_queue_name(_consumer->qname);
req.set_consumer_tag(_consumer->tag);
_codec->send(_conn,req);
waitResponse(rid);
_consumer.reset();
}
bool basicConsumer(
const std::string &consumer_tag,
const std::string &queue_name,
bool auto_ack,
const ConsumerCallback &cb){
if(_consumer.get() != nullptr){
DLOG("当前信道已订阅其他队列消息!");
return false;
}
std::string rid = UUIDHelper::uuid();
basicConsumeRequest req;
req.set_rid(rid);
req.set_cid(_cid);
req.set_queue_name(queue_name);
req.set_consumer_tag(consumer_tag);
req.set_auto_ack(auto_ack);
_codec->send(_conn,req);
basicCommonResponsePtr resp = waitResponse(rid);
if(resp->ok() == false){
DLOG("添加订阅失败!");
return false;
}
_consumer = std::make_shared<Consumer>(consumer_tag,queue_name,cb);
return true;
}
public:
void putBasicResponse(const basicCommonResponsePtr &resp){
std::unique_lock<std::mutex> lock(_mutex);
_basic_resp.insert(std::make_pair(resp->rid(),resp));
_cv.notify_all();
}
void consume(const basicConsumeResponsePtr &resp) {
if(_consumer.get() == nullptr){
DLOG("处理消息时,未找到订阅者消息!");
return;
}
if (_consumer->tag != resp->consumer_id()) {
DLOG("收到的推送消息中的消费者标识,与当前信道消费者标识不一致!");
return ;
}
_consumer->callback(resp->consumer_id(),resp->mutable_properties(),resp->body());
}
private:
basicCommonResponsePtr waitResponse(const std::string &rid){
std::unique_lock<std::mutex> lock(_mutex);
_cv.wait(lock,[&rid,this](){
return _basic_resp.find(rid) != _basic_resp.end();
});
basicCommonResponsePtr basic_resp = _basic_resp[rid];
_basic_resp.erase(rid);
return basic_resp;
}
private:
std::string _cid;
muduo::net::TcpConnectionPtr _conn;
ProtobufCodecPtr _codec;
Consumer::ptr _consumer;
std::mutex _mutex;
std::condition_variable _cv;
std::unordered_map<std::string,basicCommonResponsePtr> _basic_resp;
};
class ChannelManager{
public:
using ptr = std::shared_ptr<ChannelManager>;
ChannelManager(){}
Channel::ptr create(const muduo::net::TcpConnectionPtr &conn,
const ProtobufCodecPtr &codec) {
std::unique_lock<std::mutex> lock(_mutex);
auto channel = std::make_shared<Channel>(conn, codec);
_channels.insert(std::make_pair(channel->cid(),channel));
return channel;
}
void remove(const std::string &cid){
std::unique_lock<std::mutex> lock(_mutex);
_channels.erase(cid);
}
Channel::ptr get(const std::string &cid){
std::unique_lock<std::mutex> lock(_mutex);
auto it = _channels.find(cid);
if(it == _channels.end()){
return Channel::ptr();
}
return it->second;
}
private:
std::mutex _mutex;
std::unordered_map<std::string,Channel::ptr> _channels;
};
}
#endif
六、模块特点
- 线程安全:所有共享资源操作均加互斥锁,条件变量实现同步等待,适配多线程并发场景;
- 可靠性高:请求与响应通过rid绑定,避免错乱,析构自动清理资源,无内存泄漏;
- 接口简洁:封装全业务接口,客户端可直接调用,无需关注底层通信细节;
- 可扩展性强:模块化设计,与其他客户端模块低耦合,便于后续功能迭代;
- 可调试性好:关键操作打印日志,便于定位请求异常、消息推送失败等问题。
异步工作线程模块设计与实现
一、模块概述
AsyncWorker 是自研消息队列客户端的异步支撑核心模块 ,用于分离网络IO线程 与用户业务线程,避免网络操作阻塞业务逻辑,同时为消息消费、异步回调提供独立的线程环境。
模块封装 Muduo EventLoop 线程 + 自定义线程池,实现网络事件循环与业务任务异步化,是客户端高并发、非阻塞设计的关键组件。
二、核心功能
- 网络IO线程 :通过
EventLoopThread创建独立线程运行 Muduo 事件循环,处理网络收发、协议解析,不阻塞主线程; - 业务异步线程池:内置线程池用于处理消息消费、用户回调等耗时业务,避免阻塞IO线程;
- 轻量封装:仅做线程与事件循环的统一托管,结构简洁、无冗余逻辑。
三、核心设计
- EventLoopThread:提供独立的 Reactor 事件循环线程,负责客户端网络 IO;
- threadpool:业务线程池,负责异步执行消息回调、耗时处理;
- 统一封装为
AsyncWorker,方便上层连接、信道模块直接使用。
四、模块代码
cpp
#ifndef __M_WORKER_H__
#define __M_WORKER_H__
#include "muduo/net/EventLoopThread.h"
#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_threadpool.hpp"
namespace Fy_mq {
class AsyncWorker {
public:
using ptr = std::shared_ptr<AsyncWorker>;
// Muduo 网络事件循环线程
muduo::net::EventLoopThread loopthread;
// 自定义业务线程池
threadpool pool;
};
}
#endif
连接管理模块设计与实现
一、模块概述
客户端连接管理模块是自研消息队列客户端网络通信核心,基于Muduo网络库与Protobuf协议构建,负责客户端与服务端的TCP连接建立、连接状态维护、协议消息分发及信道统一管控。作为客户端各模块的底层支撑,该模块实现了网络IO与业务逻辑的解耦,通过同步等待机制确保连接可靠性,通过异步线程池提升消息处理效率,是客户端与服务端实现稳定通信的关键桥梁。
模块遵循高内聚、低耦合设计原则,封装了连接建立、信道管理、消息转发等核心逻辑,对外提供简洁的接口,无需上层模块关注底层网络细节,可直接与信道模块、异步工作线程模块无缝对接,支撑整个客户端的业务运转。
二、核心功能
- 连接生命周期管理:发起TCP连接请求,通过CountDownLatch同步等待连接就绪,避免未连接完成时进行业务操作;连接断开时自动释放资源,重置连接指针,保证资源安全回收。
- 协议处理与消息分发:集成Protobuf编解码器(ProtobufCodec)与消息分发器(ProtobufDispatcher),自动完成消息的序列化与反序列化,根据消息类型(通用响应、消费推送)分发至对应处理函数,支持未知消息的异常处理。
- 信道统一管控:通过信道管理器(ChannelManager),实现当前连接下所有信道的创建、查询、删除,提供openChannel、closeChannel接口,简化上层对信道的操作。
- 异步消息消费:收到服务端推送的消费消息后,提交至异步工作线程池(AsyncWorker)执行回调函数,分离网络IO线程与业务消费线程,避免阻塞网络通信。
- 异常处理:对未知协议类型的消息进行日志记录并主动关闭连接,对信道查询失败等异常场景打印日志,便于问题定位与调试。
三、核心架构设计
1. 模块依赖
- 网络依赖:Muduo网络库(TcpClient、EventLoopThread、TcpConnection),负责TCP连接建立、网络事件驱动与IO处理;
- 协议依赖:Protobuf编解码器与分发器,实现消息的编解码与类型分发;
- 内部依赖:异步工作线程模块(AsyncWorker)提供IO线程与业务线程池,信道模块(Channel)与信道管理器(ChannelManager)实现信道管控;
- 工具依赖:CountDownLatch实现连接同步等待,日志工具(ILOG/DLOG)实现异常与调试信息输出。
2. 核心成员与作用
| 核心成员 | 类型 | 作用 |
|---|---|---|
| _conn | TcpConnectionPtr | 维护客户端与服务端的TCP连接指针 |
| _latch | CountDownLatch | 同步等待连接建立完成,避免连接未就绪时执行业务操作 |
| _client | TcpClient | Muduo客户端,负责发起连接、网络消息收发 |
| _dispatcher | ProtobufDispatcher | 消息分发器,根据消息类型回调对应处理函数 |
| _codec | ProtobufCodecPtr | 协议编解码器,实现消息的序列化与反序列化 |
| _worker | AsyncWorker::ptr | 异步工作器,提供独立IO线程与业务线程池 |
| _channel_manager | ChannelManager::ptr | 信道管理器,统一管理当前连接下的所有信道 |
3. 核心流程
- 连接初始化:构造Connection对象时,传入服务端IP、端口与异步工作器,启动Muduo事件循环线程,发起TCP连接请求,通过CountDownLatch同步等待连接建立完成。
- 回调注册:注册通用响应(basicCommonResponse)、消费推送响应(basicConsumeResponse)的处理回调,以及未知消息的异常处理回调;绑定消息接收回调与连接状态变更回调。
- 信道操作:上层通过openChannel接口创建信道,由信道管理器统一管理,创建成功后完成信道握手;通过closeChannel接口关闭信道并从管理器中移除。
- 消息处理:收到服务端响应消息时,分发器根据消息类型,将通用响应转发至对应信道,将消费推送消息提交至线程池异步执行回调。
- 连接关闭:当TCP连接断开时,重置连接指针,释放相关资源,确保无内存泄漏。
四、核心接口实现
1. 构造函数
初始化Muduo客户端、消息分发器、协议编解码器,注册各类回调函数,发起TCP连接并同步等待连接就绪,确保连接建立后再进行后续业务操作。核心逻辑包括绑定消息接收、连接状态变更回调,注册响应消息处理函数,完成客户端初始化。
2. 信道操作接口
- openChannel:通过信道管理器创建信道,调用信道的openChannel方法完成握手,若握手失败则返回空指针,打印调试日志。
- closeChannel:调用信道的closeChannel方法关闭信道,再通过信道管理器移除该信道,释放信道资源。
3. 响应处理接口
- basicResponse:接收服务端通用响应消息,根据消息中的信道ID(cid)查询对应的信道,将响应消息转发至该信道,唤醒信道中等待响应的线程。
- consumeResponse:接收服务端消费推送消息,查询对应的信道,将消息处理逻辑提交至异步线程池,避免阻塞网络IO线程。
- onUnknownMessage:处理未知类型的消息,打印日志并主动关闭连接,提升系统健壮性。
4. 连接状态回调
- onConnection:监听TCP连接状态,连接建立时唤醒等待线程、保存连接指针;连接断开时重置连接指针,释放连接资源。
五、模块代码
cpp
#ifndef __M_CONNECTION_H__
#define __M_CONNECTION_H__
#include "muduo/proto/dispatcher.h"
#include "muduo/proto/codec.h"
#include "muduo/base/Logging.h"
#include "muduo/base/Mutex.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/EventLoopThread.h"
#include "muduo/base/CountDownLatch.h"
#include "mq_channel.hpp"
#include "mq_worker.hpp"
namespace Fy_mq{
class Connection{
public:
using ptr = std::shared_ptr<Connection>;
using MessagePtr = std::shared_ptr<google::protobuf::Message>;
Connection(const std::string &sip,int sport,const AsyncWorker::ptr &worker):
_latch(1),_client(worker->loopthread.startLoop(),muduo::net::InetAddress(sip,sport),"Client"),
_dispatcher(std::bind(&Connection::onUnknownMessage,this,std::placeholders::_1,
std::placeholders::_2,std::placeholders::_3)),
_codec(std::make_shared<ProtobufCodec>(std::bind(&ProtobufDispatcher::onProtobufMessage,&_dispatcher,
std::placeholders::_1,std::placeholders::_2,std::placeholders::_3))),
_worker(worker),
_channel_manager(std::make_shared<ChannelManager>()){
_dispatcher.registerMessageCallback<basicCommonResponse>(std::bind(&Connection::basicResponse,this,
std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
_dispatcher.registerMessageCallback<basicConsumeResponse>(std::bind(&Connection::consumeResponse,this,
std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
_client.setMessageCallback(std::bind(&ProtobufCodec::onMessage,_codec.get(),
std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
_client.setConnectionCallback(std::bind(&Connection::onConnection,this,std::placeholders::_1));
_client.connect();
_latch.wait();
}
Channel::ptr openChannel(){
Channel::ptr channel = _channel_manager->create(_conn,_codec);
bool ret = channel->openChannel();
if(ret == false){
DLOG("打开信道失败!");
return Channel::ptr();
}
return channel;
}
void closeChannel(const Channel::ptr &channel){
channel->closeChannel();
_channel_manager->remove(channel->cid());
}
private:
void basicResponse(const muduo::net::TcpConnectionPtr &conn,const basicCommonResponsePtr &message,muduo::Timestamp) {
//1.找到信道
Channel::ptr channel = _channel_manager->get(message->cid());
if(channel.get() == nullptr){
DLOG("未找到信道消息!");
return;
}
//2.将得到的响应对象,添加到信道的基础响应hash_map中
channel->putBasicResponse(message);//此时会唤醒等待的线程
}
void consumeResponse(const muduo::net::TcpConnectionPtr &conn,const basicConsumeResponsePtr &message,muduo::Timestamp){
Channel::ptr channel = _channel_manager->get(message->cid());
if(channel.get() == nullptr){
DLOG("未找到信道消息!");
return;
}
_worker->pool.push([channel,message](){
channel->consume(message);
});
}
void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn, const MessagePtr& message, muduo::Timestamp) {
ILOG("onUnknownMessage: %s",message->GetTypeName().c_str());
conn->shutdown();
}
void onConnection(const muduo::net::TcpConnectionPtr&conn){
if (conn->connected()) {
_latch.countDown();//唤醒主线程中的阻塞
_conn = conn;
}else {
//连接关闭时的操作
_conn.reset();
}
}
private:
muduo::net::TcpConnectionPtr _conn;//客户端对应的连接
muduo::CountDownLatch _latch;//实现同步的
muduo::net::TcpClient _client;//客户端
ProtobufDispatcher _dispatcher;//请求分发器
ProtobufCodecPtr _codec;//协议处理器
AsyncWorker::ptr _worker;
ChannelManager::ptr _channel_manager;
};
}
#endif