Skip to content

实验总览

本页结论:hello-mq 的全部实验使用统一入口、统一日志与统一断言;本页给出实验清单、分级与运行方式。

为什么需要实验

消息系统的很多结论都附带前置条件:哪个 Broker 版本、什么配置、客户端如何确认。没有可复现实验支撑的结论,在本仓库只会以「规范层面」标注,不会写成事实断言。因此每个关键行为尽量落到一次可以重复执行的 Docker 实验,并把归一化后的快照提交进仓库(见 证据政策)。

实验清单

产品实验等级验证目标
RabbitMQbasicL1durable 队列 + Publisher Confirms + 手动 ACK + 幂等落库的最小闭环
RabbitMQroutingL1Topic Exchange 按路由键把同一事件分发给不同绑定
RabbitMQconsumer-crashL2消费者在业务提交后、ACK 前崩溃 → 重投递 → 幂等表拦截
RabbitMQretry-dlqL2毒消息经 TTL+DLX 重试环最终进入死信队列
RabbitMQbacklog-recoveryL2无消费者时消息积压,消费者恢复后追赶清零
KafkabasicL1acks=all + 幂等生产 + 手动提交 offset + 幂等落库的最小闭环
Kafkaconsumer-groupL2同组两个消费者瓜分分区,独立组各自全量接收
Kafkaordering-replayL2同 key 消息进同一分区且保序,新消费组从 earliest 全量回放
Kafkaidempotence-transactionL2事务提交消息对 read_committed 可见,中止事务消息不可见
RocketMQbasicL1Normal topic + SimpleConsumer + 幂等落库的最小闭环
RocketMQfifo-delayL2FIFO topic 同 MessageGroup 保序,延迟消息按设定时间投递
RocketMQtransactionL2Half Message 首查 UNKNOWN、回查后 COMMIT,消息恰好消费一次
RocketMQretry-dlqL2消费失败按内置重试重投,达上限进入 %DLQ% 组 topic
PulsarbasicL1Exclusive 订阅 + 业务提交后才 ack + 幂等落库的最小闭环
PulsarsubscriptionsL2四类订阅对比:Exclusive/Shared/Failover/Key_Shared
Pulsarredelivery-replayL2negativeAck 重投达上限进 DLQ,reset-cursor 全量回放

等级定义见 实验约定:L0 静态检查、L1 单节点冒烟、L2 可靠性行为、L3/L4 默认不执行。

运行方式

bash
# 列出全部实验
npm run lab -- list

# 运行单个实验
npm run lab -- rabbitmq basic

# 运行某产品全部实验
npm run lab -- rabbitmq all

# 清理某产品的实验资源(仅删除本项目 Compose Project)
npm run lab -- rabbitmq clean

每个实验都会:

  1. 用项目名隔离的 Compose Project(hello-mq-<product>-<lab>)启动 Broker;
  2. 轮询健康检查而不是固定等待;
  3. 先声明拓扑,再运行 Producer 与 Consumer(宿主机 JVM 进程,见 实验约定);
  4. 执行业务级断言(数量、幂等表行数、队列深度、重投次数),输出 PASS/FAIL;
  5. 正常路径自动停止并删除容器,把归一化快照写入 outputs/<product>/<lab>.snapshot

断言原则

「进程退出码为 0」不等于实验成功。每个实验至少断言:生产确认数、消费数量与唯一 messageId 数、业务落库行数、产品侧状态(队列深度、x-death 计数等),以及失败注入确实发生(如崩溃退出码 137)。

快照如何阅读

快照由 frontmatter(状态、镜像、断言)与归一化日志两部分组成。归一化会把时间戳替换为 <ts>、messageId 替换为 mid-N、容器标识替换为 <cid>,保证不同机器上运行得到可比较的结果。每个实验页内嵌一份当时提交的快照:

verifiedrabbitmq / basicbroker 4.1.4 · java-amqp-client-5.34.0
镜像rabbitmq:4.1.4-management@sha256:294b01e1796a8acede4619f32a1c394fae1f8021e57986ea01aad38dc2a4f502
捕获时间2026-08-19T07:54:54.581Z
耗时 / 退出码13080 ms / exit 0
断言
confirmed3
received3
uniqueMessageIds3
redeliveredCount0
businessCommitted3
duplicatesSkipped0
business_rows3
queueDepthAfter0
consumerExitCode0
归一化日志
[producer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-1 eventType=order.created schemaVersion=1 aggregateId=order-1001 traceId=trace-1 correlationId=order-1001 destination=orders.basic routingKey=orders.basic durationMs=<ms> status=confirmed
[producer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-2 eventType=order.created schemaVersion=1 aggregateId=order-1002 traceId=trace-2 correlationId=order-1002 destination=orders.basic routingKey=orders.basic durationMs=<ms> status=confirmed
[producer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-3 eventType=order.created schemaVersion=1 aggregateId=order-1003 traceId=trace-3 correlationId=order-1003 destination=orders.basic routingKey=orders.basic durationMs=<ms> status=confirmed
[producer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic destination=orders.basic confirmed=3 status=done
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-1 eventType=order.created schemaVersion=1 aggregateId=order-1001 traceId=trace-1 correlationId=order-1001 destination=orders.basic consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-1 eventType=order.created schemaVersion=1 aggregateId=order-1001 traceId=trace-1 correlationId=order-1001 destination=orders.basic attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-2 eventType=order.created schemaVersion=1 aggregateId=order-1002 traceId=trace-2 correlationId=order-1002 destination=orders.basic consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-2 eventType=order.created schemaVersion=1 aggregateId=order-1002 traceId=trace-2 correlationId=order-1002 destination=orders.basic attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-3 eventType=order.created schemaVersion=1 aggregateId=order-1003 traceId=trace-3 correlationId=order-1003 destination=orders.basic consumer=consumer-1 attempt=1 redelivered=false status=received
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic messageId=mid-3 eventType=order.created schemaVersion=1 aggregateId=order-1003 traceId=trace-3 correlationId=order-1003 destination=orders.basic attempt=1 status=business_committed
[consumer] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic queue=orders.basic received=3 status=done
[inspect] timestamp=<ts> level=INFO service=order-service product=rabbitmq lab=basic business_rows=3 processed_rows=3 status=snapshot
[assert] confirmed=3 PASS
[assert] received=3 PASS
[assert] uniqueMessageIds=3 PASS
[assert] redeliveredCount=0 PASS
[assert] businessCommitted=3 PASS
[assert] duplicatesSkipped=0 PASS
[assert] business_rows=3 PASS
[assert] queueDepthAfter=0 PASS
[assert] consumerExitCode=0 PASS
npm run lab -- rabbitmq basic

想在自己的机器上得到同样的结果,运行页面底部的复现命令即可。

下一步

以统一实验验证消息系统语义边界