经典队列做单机存储,Quorum Queue 用 Raft 做强一致复制,这是 RabbitMQ 在「队列」这个模型内的两种实现。但 RabbitMQ 3.9 引入了第三种类型——Stream,它代表了完全不同的思路:不再是队列,而是日志

Stream 的出现,是 RabbitMQ 对 Kafka 模式的正面回应。理解 Stream 的关键,是理解它和队列在消息语义上的根本差异——这个差异决定了它适合什么、不适合什么。这一篇把这个差异讲透。

队列 vs 日志:消费语义的根本差别

队列和日志,是两种完全不同的消息模型:

队列(Queue)语义

  • 消息是「易失的数据项」——被消费就消失。
  • 一条消息被一个消费者 ack 后,从队列删除。
  • 无法重新消费已经处理过的消息(删了就是删了)。
  • 适合「任务分发」——每条消息代表一个要处理的任务,处理完即弃。

日志(Log)语义

  • 消息是「不可变的事件记录」——追加到日志后长期保留。
  • 消费者按 offset(偏移量)读取,读过的消息不消失。
  • 可以反复重放——回到任意 offset 重新消费。
  • 适合「事件溯源」「数据回放」「多消费者独立读」。

RabbitMQ 的经典队列和 Quorum Queue 都是队列语义(消费即删)。Stream 是日志语义(消费不删,按 offset 读)。这个差别是理解 Stream 的一把钥匙。

Stream 怎么工作

Stream 的核心是一个追加写入的日志文件

  • 生产者发消息,broker 把消息顺序追加到 Stream 的日志(不是随机写)。
  • 消费者通过一个 offset(日志位置)读取消息。
  • 消费者维护自己的 offset,读到哪里了、要不要重读,都由消费者控制。
  • broker 按保留策略(时间或大小)清理过期的日志段。

因为是顺序追加 + 按 offset 读,Stream 的吞吐可以做到很高(顺序 IO 远快于随机 IO)。这也是 Kafka 能做到百万级吞吐的原因——同样的日志模型。

Stream 的几个关键特性:

多消费者独立读。 多个消费者可以各自维护自己的 offset,独立读同一个 Stream,互不影响。A 消费者读到 offset 100,B 消费者可以从 offset 0 开始读,互不干扰。这是日志模型相对队列模型的一大优势——队列里多个消费者是竞争(共享消息),日志里多个消费者是独立(各读各的)。

消息重放。 消费者可以把 offset 回退,重新消费之前读过的消息。比如消费逻辑有 bug,修复后想重新处理一遍历史数据,把 offset 回退即可。队列语义做不到这个(消息早删了)。

保留策略。 Stream 按 max-age(最大保留时间)或 max-length-bytes(最大字节数)清理日志。在这个保留窗口内,消息一直在,可以被任意次数消费。

队列 vs Stream:消费语义对比

Stream 解决了队列做不到的什么

Stream 的价值,在于它覆盖了队列模型力所不及的几类场景:

场景一:事件溯源与审计。 业务事件需要长期保留、可回溯审计。队列消费即删,无法回溯;Stream 的日志天然是审计记录,保留期内可随时查历史。

场景二:多下游各自消费同一份数据。 一个事件流,分析、归档、通知三个下游各自消费。队列里要么广播(每个下游一个队列,消息复制三份),要么竞争(共享一个队列,每个下游只拿三分之一)。Stream 里三个下游各自维护 offset 读同一个 Stream,零复制,各自独立。

场景三:消费逻辑修复后的数据回填。 发现消费逻辑有 bug,已经错误处理了一批数据。修复后想把那批数据重新处理一遍——Stream 回退 offset 即可;队列里那些数据早删了,回填得从源头重发。

场景四:大数据/流式处理。 Stream 的日志模型和 Kafka 一致,能对接 Flink、Spark 这类流处理框架,做窗口计算、状态聚合。队列模型不适合这些(消息即删,无法窗口化)。

Stream 不是用来替代队列的

虽然 Stream 功能强,但它不是「更好的队列」,而是不同模型的工具。很多队列擅长的场景,Stream 反而不适合:

  • 任务分发:任务处理完就该消失,Stream 里任务不消失会重复处理(要靠业务幂等兜底,徒增复杂度)。
  • 消费确认的可靠性:队列的 ack 机制保证「处理失败重投」,Stream 没有 ack,消费失败要靠 offset 管理自己处理,更复杂。
  • 低延迟的即产即消:队列对「消息进来立刻消费」优化得更好。

所以正确的认知是:Stream 补齐了 RabbitMQ 在「日志/流处理」场景的能力,但它和队列是互补的,不是替代关系。任务分发用队列(经典或 Quorum),数据流和事件溯源用 Stream。

Stream 的代价与边界

Stream 的代价:

保留成本。 消息长期保留在日志里,磁盘占用随时间增长。要合理设保留策略,否则磁盘会被撑爆。

消费者复杂度。 消费者要自己管理 offset(存哪、怎么恢复、怎么回退),比队列的 ack 模型复杂。RabbitMQ 的 Stream 客户端(rabbitmq-stream)封装了 offset 管理,但底层概念还是要理解。

顺序与可靠性模型不同于队列。 单个 Stream 内消息严格按写入顺序,消费者按 offset 顺序读到——单流内有序;但跨 super stream 的多个分区不保证全局有序,这点和 Kafka 一致。可靠性上,Stream 不是「处理失败重投」,而是「消费者自己决定 offset」,没有队列那种 ack 重投闭环。设计时要适应这套流式模型。

与 Quorum 的定位区分。 两者都有复制(Stream 也是多副本),但 Stream 的复制是「为日志的持久和高可用」,Quorum 的复制是「为队列消息的强一致」。别混淆——需要队列语义选 Quorum,需要日志语义选 Stream。

三种队列类型的选型总览

到这里,RabbitMQ 的三种主要队列类型都讲完了。收一个选型总览:

类型 语义 复制 适用场景 不适用
经典队列 队列(消费即删) 无(单机)/ 已废弃镜像 单机任务队列、临时队列、低延迟在线 高可用、海量积压
Quorum Queue 队列(消费即删) Raft 强一致 核心业务队列、不能丢消息 海量吞吐、临时队列
Stream 日志(按 offset 读、可重放) 多副本日志 事件溯源、多消费者、数据回填、流处理 任务分发、ack 重投

这个表的判断逻辑:先确定场景是「任务/消息分发」(队列语义)还是「事件/数据流」(日志语义),再在队列里按「是否需要高可用」选经典或 Quorum。不要拿着「哪个最好」的心态选,而是按场景匹配。

收束:Stream 让 RabbitMQ 覆盖了日志场景

Stream 的引入,让 RabbitMQ 不再局限于队列模型,而是能同时服务「队列」和「日志」两类需求。对于已经在用 RabbitMQ 的团队,这意味着不必为了事件溯源再引入一套 Kafka——用 Stream 就行。但 Stream 不是队列的升级,理解两者语义差别,才能用对场景。

阶段三「存储与吞吐」到这里结束。下一阶段往上走一层,讲应用侧怎么用好 RabbitMQ——连接、信道、连接池、消费者并发、流控,那是客户端与性能的篇章。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。我相信 AI 是程序员的最佳搭档。想跟完这套「图解 RabbitMQ」,欢迎关注公众号 「十三Tech」

十三Tech公众号二维码