【BitMQ 消息队列系统】测试报告

一、项目背景

本项目是一个基于 C++ 实现的轻量级消息队列(Message Queue)系统,仿照 RabbitMQ 核心架构设计。项目实现三大核心能力:消息发布与路由 (DIRECT / FANOUT / TOPIC 三种交换机模式)、消息持久化与消费 (支持自动确认 / 手动确认)、多信道连接复用(单 TCP 连接承载多信道)。底层基于 Muduo 高性能网络库,使用 Protobuf 作为序列化协议,元数据存储使用 SQLite,消息体以自定义二进制文件格式持久化。项目为学习型框架,核心价值在于从零理解消息队列的交换机路由、绑定机制、持久化策略和消费者组调度。

二、项目简介

本项目包含以下核心功能模块:

  • 消息发布模块:客户端通过 TCP 长连接与服务端通信,通过信道(Channel)声明交换机、队列和绑定关系,发布消息时由 Router 根据交换机类型(DIRECT/FANOUT/TOPIC)和 routing_key 路由到目标队列。

  • 消息消费模块:支持消费者订阅队列,服务端通过 ConsumerManager 管理消费者组,采用轮询(Round-Robin)策略向消费者推送消息。支持自动确认(auto_ack)和手动确认(basicAck)两种模式。

  • 虚拟主机模块:VirtualHost 作为资源隔离容器,管理所属的交换机、队列、绑定关系和消息数据,一个服务端可承载多个虚拟主机。

  • 持久化层 :元数据(交换机、队列、绑定)存储在 SQLite 数据库中;消息体存储在自定义二进制文件中(格式:| 消息长度(8B) | 消息体 |),支持有效消息标记和垃圾回收。

  • 协议层:基于 Protobuf 定义请求/响应协议(mq_proto.proto、mq_msg.proto),使用 Muduo 的 ProtobufCodec 进行编解码和消息分发。

三、测试计划

3.1 测试策略

采用白盒集成测试 + 黑盒性能压测 双轨策略。白盒部分基于项目已有单元测试(gtest)验证各模块逻辑正确性;黑盒部分通过自研压测客户端 benchmark_client 对整体链路进行性能评估,覆盖不同并发连接数下的吞吐量、延迟和稳定性。

3.2 测试环境

组件 说明
服务端 Ubuntu 24.04 云服务器,C++ (g++ 13.3, C++17)
压测客户端 同机部署,基于项目客户端 SDK 自研
通信方式 localhost TCP,Protobuf 协议
服务端口 8085
测试模式 FANOUT 交换机,非持久化(纯内存路径)
编译模式 Debug (-g -o0)

3.3 提测方式

分两层递进测试:单元测试 → 性能压测。先执行已有 gtest 单元测试确认各模块逻辑正确,再通过自研压测工具进行递增并发压测,最后结合代码审查识别架构层面问题。

四、测试工具

工具 用途
Google Test (gtest) 服务端模块单元测试(10 个测试用例)
benchmark_client (自研 C++) 多连接并发 QPS 压测
Makefile + g++ 编译构建与依赖管理
std::chrono + std::atomic 压测计时与计数
代码审查 (Code Review) 架构合理性分析、Bug 发现、瓶颈识别

五、测试类型与覆盖情况

测试类型 覆盖范围 发现问题数量 备注
单元测试 Exchange / Queue / Binding / Message / Host / Route / Consumer / Channel / Connection / File 十个模块 2(编译级) protobuf 版本不兼容导致 2 个用例编译失败
功能测试 编译构建、服务启动、TCP 建连、信道创建、消息发布与响应 0 核心链路功能正常
并发测试 1/2/5/10 连接并发压测 1 10 连接触发客户端 segfault
性能测试 4 档递增连接数(1/2/5/10) 0 10 连接档位崩溃未完成
代码审查 全量源码(mqserver / mqclient / mqcommon) 3 GC 逻辑错误、线程池默认值、返回值遗漏

六、单元测试

6.1 测试编译情况

用例 ID 用例名称 测试模块 编译结果 说明
TC-U-001 mq_filetest 文件操作 PASS 编译通过
TC-U-002 mq_exchangetest 交换机管理 FAIL std::unordered_map 无法隐式转为 protobuf::Map
TC-U-003 mq_queuetest 队列管理 PASS 编译通过
TC-U-004 mq_bindingtest 绑定管理 PASS 编译通过(有 warning)
TC-U-005 mq_msgtest 消息管理 PASS 编译通过
TC-U-006 mq_hosttest 虚拟主机 FAIL 同 TC-U-002,protobuf API 不兼容
TC-U-007 mq_routetest 路由算法 PASS 编译通过(有 warning)
TC-U-008 mq_consumertest 消费者管理 PASS 编译通过(有 warning)
TC-U-009 mq_channeltest 信道管理 PASS 编译通过
TC-U-010 mq_connectiontest 连接管理 PASS 编译通过

测试结果:8/10 编译通过,2 例因系统 protobuf 版本(3.21)与项目使用版本(3.20)API 差异导致编译失败,非代码逻辑错误。

七、性能测试

7.1 测试方案

采用递增连接数压力模型,从 1 连接逐步提升至 10 连接,固定每连接发送 10,000 条消息。所有连接在同一时刻并发启动,每个连接独立完成 TCP 建连 → 打开信道 → 声明 FANOUT 交换机 → 声明队列 → 绑定 → 循环发布消息 → 关闭信道流程。通过 std::atomic<uint64_t> 全局计数器汇总成功消息数,std::chrono::steady_clock 计算端到端耗时。

复制代码
压测客户端启动方式:
复制代码
./benchmark_client [ip] [port] [连接数] [总消息数]

每个连接内部流程:

复制代码
benchmark_client
  └─ worker (per thread, per connection)
       ├─ AsyncWorker 创建(含 EventLoopThread + threadpool)
       ├─ Connection 创建(TcpClient 连接服务端)
       ├─ Channel::openChannel()     ← 打开信道
       ├─ declareExchange(FANOUT)    ← 声明交换机
       ├─ declareQueue()             ← 声明队列
       └─ for i in 0..N:
            └─ basicPublish(exchange, body)  ← 同步发布(发送 + waitResponse)

7.2 性能测试结果汇总

递增连接数压力测试

档位 连接数 总消息数 耗时 (s) 吞吐量 (msg/s) 平均延迟 (ms) 错误数 结果
基线 1 1,000 0.199 5,025 0.20 0 PASS
A档 2 10,000 2.049 4,880 0.20 0 PASS
B档 5 50,000 3.779 13,231 0.08 0 PASS
C档 10 100,000 --- --- --- --- CRASH

7.3 关键发现

  1. 吞吐量不随连接数线性增长:1 连接 5,025 QPS,2 连接仅 4,880 QPS(甚至微降),证明瓶颈在服务端而非客户端。5 连接时达 13,231 QPS(2.6x),说明多连接能部分并行,但远非线性扩展。

  2. 单 EventLoop 瓶颈明显 :服务端 mq_broker.hpp 中使用单一 _baseloop.loop() 处理所有连接的 I/O 和业务逻辑,每条约 100μs 的处理时间决定了吞吐量上限 ≈ 10,000 msg/s。

  3. Debug 编译的影响 :当前为 -g -O0 Debug 编译,Release 编译(-O2)预计 QPS 可达 2~3 倍提升。

  4. C 档崩溃定位:客户端在创建第 8 个 TcpClient 时触发 segfault,堆栈指向 Muduo Connector 初始化阶段,疑为 Muduo 静态库在多线程并发创建 TcpClient 时的内部竞争条件。

八、项目测试 Bug 简述

8.1 Bug 优先级统计

本次测试共发现 3 个 Bug,其中 P0 级(逻辑错误)1 个,P1 级(性能隐患)1 个,P3 级(代码规范)1 个。

优先级 数量 说明
P0(逻辑错误) 1 GC 条件判断反向
P1(性能隐患) 1 线程池默认 1 线程
P3(代码规范) 1 declareExchange 忽略返回值

8.2 Bug 详情列表

BUG-001 (P0):消息文件 GC 触发条件逻辑反转

字段 内容
位置 mqserver/mq_message.hpp:324 QueueMessage::GCCheck()
复现条件 持久化消息数量 > 2000 时触发 GC 判断
现象 有效消息 > 50% 时触发 GC(不必要的文件重写);有效消息 < 50% 时反而不触发 GC(文件空洞永不清理)
根因 条件 _valid_count * 10 / _total_count > 5 应改为 < 5_valid_count 为有效消息数,_total_count 为总消息数(含已删除空洞),GC 应在空洞较多时触发
修复方案 将判断条件改为 _valid_count * 10 / _total_count < 5

BUG-002 (P1):线程池默认仅为 1 线程

字段 内容
位置 mqcommon/mq_threadpool.hpp:17 + mqserver/mq_broker.hpp:36
现象 threadpool 构造函数默认参数 threadnum = 1,Server 实例化时未传参,导致消息消费推送完全串行化,多核 CPU 无法利用
修复方案 Server 构造时传入 std::thread::hardware_concurrency() 或合理默认值(如 4)

BUG-003 (P3):declareExchange 返回值被忽略

字段 内容
位置 mqserver/mq_channel.hpp:62-66
现象 declareExchange() 调用 _host->declareExchange() 获取返回值 ret,但始终 basicResponse(true, ...),忽略了实际返回值
修复方案 改为 basicResponse(ret, req->rid(), req->cid())

九、遗留问题

以下为代码审查中识别的架构层面待改进项,非本次测试发现的阻塞性缺陷:

编号 问题 优先级 说明
1 单 EventLoop 架构 P1 所有连接的 I/O 和业务逻辑在同一线程串行处理,无法利用多核。Muduo 支持 setThreadNum(N) 开启多 Reactor,本项目未使用
2 同步文件 I/O 阻塞 IO 线程 P1 持久化消息的 FileHelper::write() 在 EventLoop 线程中同步执行,阻塞所有其他连接。应改为异步刷盘或独立 I/O 线程
3 无消息流控 / 背压机制 P1 _msgs 为无界 std::list,生产者速度超过消费者时无限增长至 OOM
4 客户端同步等待响应 P2 Channel::basicPublish() 使用条件变量同步等待 basicCommonResponse,限制单连接吞吐量。可改为异步回调或批量 ACK
5 Protobuf 多次序列化 P2 单条消息链路经历客户端序列化 → 服务端反序列化 → 封装序列化 → 客户端反序列化,共 4 次操作
6 无连接池 / 连接复用 P2 压测客户端每次新建连接和信道,未实现连接复用
7 文件无分片机制 P3 持久化文件持续追加写入,无大小限制和分片策略
8 write 无 fsync P2 持久化仅 write() 到 page cache,OS 崩溃可能丢数据
9 无 RPC 超时机制 P2 waitResponse() 无超时保护,服务端无响应时客户端永久阻塞

十、测试结论

整体测试结果 :本次项目测试条件通过。核心消息发布链路(编译→启动→建连→发布→响应)功能正常,纯内存非持久化场景下 QPS 达 5,000 ~ 13,000。单元测试 8/10 通过(2 例因 protobuf 版本差异编译失败,非逻辑错误)。

性能瓶颈:吞吐量瓶颈明确为服务端单 EventLoop 架构,单核处理上限约 10,000 msg/s(Debug 编译)。持久化模式下受同步文件 I/O 影响,QPS 预计降至 200 ~ 5,000(视磁盘类型)。

Bug 情况:共发现 P0 级 Bug 1 个(GC 逻辑反转)、P1 级 Bug 1 个(线程池默认 1 线程)、P3 级 Bug 1 个(返回值忽略)。另识别架构改进项 9 个,均不影响当前基本功能。

耗时统计:项目测试耗时约 1.5 小时(含编译 5min + 环境准备 10min + 单元测试 20min + 性能压测 30min + 代码审查 25min)。

项目补充信息

项目 信息
项目框架 C++ (g++ 13.3, C++17),Muduo 网络库,Protobuf 3.20,SQLite3
代码仓库 mq/ 目录,服务端/客户端/公共库分离结构
压测工具 mqclient/benchmark_client.cc,自研 C++ 多连接并发工具
单元测试 mqtest/ 目录,GTest 框架,共计 10 个模块测试

测试总结与优化建议

  1. 优先修复 P0 Bug(GC 条件反转),避免持久化场景下文件空洞无法回收导致的磁盘空间浪费

  2. 调整线程池大小至 hardware_concurrency(),可立即提升消费推送并发度

  3. 后续应在 Release 编译下重新压测,获取更接近生产环境的 QPS 数据

  4. 建议补充持久化模式(DURABLE)下的性能测试,评估同步文件 I/O 对吞吐量的实际影响

  5. 可引入 JMeter 或 wrk 进行更规范的压力模型测试(需先实现 HTTP 协议适配层)

  6. 建议对有消费者在线场景进行端到端压测,覆盖完整的 publish → route → push → consume 链路

本报告测试数据均来源于实际执行结果。压测工具代码位于 mqclient/benchmark_client.cc,可重复执行验证。