系列导航:

到了第三篇 overview,问题已经从“为什么需要消息队列”与“消息如何流转”进入到真正影响业务建模的部分:拓扑怎么设计。也就是,消息先发到哪种 Exchange、应该建几个 Queue、多个消费者到底是在竞争消费还是在做发布订阅。

本文只回答这些业务拓扑问题,不展开消费确认、prefetch、重试、顺序和生产可靠性。你可以把它理解成 RabbitMQ 的“结构设计篇”。

继续使用 order.created 这个共享示例

三篇 overview 文章都使用同一个事件:

{
  "eventId": "01J...",
  "eventType": "order.created",
  "occurredAt": "2026-07-15T10:00:00Z",
  "orderId": "ORD-20260715-001",
  "customerId": "C10086"
}

假设订单服务发布这个事件后,有三个下游业务:

  • 库存系统要预留库存
  • 通知系统要发送消息
  • 审计系统要记录事件

真正要设计的不是“消息有没有发出去”,而是下面三个问题:

  1. 这三个业务应该共用一个 Queue,还是各自独立?
  2. 订单事件应该发到哪种 Exchange?
  3. 某个业务如果有多个实例,它们之间算一份订阅还是多份订阅?

第一原则:按业务能力拆独立 Queue

只要两个处理链路的职责不同,就应该优先考虑给它们独立 Queue。

order.created 为例,更合理的拓扑通常是:

Exchange: orders.events
  -> Queue: inventory.order-events -> 库存服务实例们
  -> Queue: notification.order-events -> 通知服务实例们
  -> Queue: audit.order-events -> 审计服务实例们

这样设计有四个直接好处:

  • 库存积压不会堵住通知
  • 审计暂时下线不会影响库存
  • 每个业务都能按自己的节奏扩缩容
  • 后续新增订阅方时,不需要回头重构已有消费者

一个常见误区是让多个业务直接共用同一个 Queue。这样做虽然省了声明拓扑的步骤,但会把原本应该独立演进的业务硬绑在一起,导致消息分流、回放、扩容、排障都变难。

所以,Queue 首先应该表达业务处理边界,而不是节省几个配置项。

竞争消费和发布订阅不是一回事

理解 Queue 设计时,最容易混淆的就是这两个概念。

发布订阅与竞争消费的两层拓扑上层表示一个 Exchange 将同一事件复制到三个业务队列,下层表示一个库存队列中的消息由三个库存消费者实例竞争处理。BUSINESS SUBSCRIPTIONS · 一份事件复制到多个业务 Queueorders.eventsExchangeorder.createdinventory.queue独立积压 · 独立重放库存业务 Consumernotification.queue独立积压 · 独立重放通知业务 Consumeraudit.queue独立积压 · 独立重放审计业务 ConsumerCOMPETING CONSUMERS · 同一 Queue 内每条消息只交给一个实例inventory.queuereadyInventory 实例 AInventory 实例 BInventory 实例 C一条消息 → 一个实例扩容提升吞吐,不复制业务订阅业务维度与实例维度分开设计发布订阅与竞争消费的两层拓扑移动布局中先展示三个业务队列,再展示一个队列内的三个竞争消费者。BUSINESS SUBSCRIPTIONSorders.events同一 order.created 复制到三个 Queueinventory.queue独立业务订阅库存业务notification.queue独立业务订阅通知业务audit.queue独立业务订阅审计业务COMPETING CONSUMERSinventory.queue同一业务的一份消息流Inventory 实例 AInventory 实例 BInventory 实例 C每条消息只交给其中一个实例增加实例提升吞吐,不增加业务副本
发布订阅与竞争消费的两层拓扑先按业务能力拆 Queue,再在单个 Queue 内增加 Consumer 实例扩吞吐,两层语义不要混淆。
同一业务事件可以被路由到多个独立队列;同一队列内部的多个消费者实例竞争处理其中的消息。

竞争消费:多个实例共同处理同一份工作

如果库存服务部署了 3 个实例,并且它们都消费 inventory.order-events,那么它们是在竞争同一个 Queue 里的消息。

这意味着:

  • 这 3 个实例属于同一个业务订阅
  • 每条消息通常只会被其中一个实例处理
  • 目的是提升吞吐和可用性,而不是复制消息

发布订阅:多个业务都要收到一份消息

如果库存、通知、审计都要处理 order.created,那就不是“让三个服务一起抢一个 Queue”,而是应该让 Exchange 把消息分别路由到三个独立 Queue。

这意味着:

  • 三个业务是三份独立订阅
  • 每个业务都应拥有自己的一份消息副本
  • 每个业务内部是否再做多实例竞争消费,是各自的实现问题

一句话区分:

  • 同一个业务的多个实例,共享一个 Queue,叫竞争消费
  • 不同业务各自拥有独立 Queue,叫发布订阅

Direct、Topic、Fanout、Headers 到底怎么选

Exchange 类型不是“高级版和低级版”的关系,而是不同的路由语义。判断标准不是性能想象,而是业务拓扑到底想表达什么。

精确匹配

Direct|精确匹配一个明确的路由键

当事件名或命令名已经稳定,而且订阅规则只需要判断“是否完全相等”时,Direct 通常最直接。

判断信号
routing key 必须与 binding key 精确相等,不需要通配或多条件组合。
典型表达
order.created
适合场景
事件名固定明确的业务事件、任务或命令投递。
使用边界
事件层级持续增加时,精确绑定会越来越零散,需要维护更多规则。
层级模式

Topic|按层级模式订阅一类事件

当事件名天然具有业务域和动作层级时,Topic 可以用通配规则表达一类订阅,而不必逐条声明精确绑定。

判断信号
订阅方想匹配某个业务域、某一层事件,或跨层级的一组事件。
典型表达
order.*
适合场景
订单、支付等业务事件按层级命名,并需要按类别订阅。
使用边界
如果事件命名缺少稳定约定,通配模式会迅速失去可读性和准确性。
全部广播

Fanout|不做筛选,广播给所有绑定队列

当所有绑定方都应该收到同一条消息时,Fanout 直接复制到每个绑定 Queue,不要求发布方设计路由键。

判断信号
所有订阅方都需要收到消息,不存在“谁该收到、谁不该收到”的筛选问题。
典型表达
routing key 会被忽略
适合场景
配置刷新、缓存失效和系统范围内的统一广播。
使用边界
一旦不同订阅方需要不同筛选规则,就应该改用 Direct 或 Topic。
属性组合

Headers|按多个消息头条件组合匹配

当路由依赖地区、租户、优先级或来源系统等多个属性,而且单个路由键难以自然表达时,再考虑 Headers。

判断信号
匹配条件来自多个消息头,需要按全部满足或任一满足组合判断。
典型表达
x-match: all / any
适合场景
多维属性确实无法清晰编码进事件名或 routing key 的路由。
使用边界
规则更难观察和解释,常规业务事件不应为了灵活性优先采用。

默认 Exchange 的边界:能直达 Queue,不适合做业务拓扑

默认 Exchange 很适合最简单的点对点场景:你已经知道目标 Queue 名称,并且就是想把消息送过去。

例如:

publish(exchange = "", routingKey = "inventory.order-events")

这类方式的边界也很明显:

  • 发布方必须知道具体 Queue 名称
  • 新增一个下游时,发布方往往也要跟着改
  • 不适合表达“一条事件由多个业务分别订阅”的拓扑

所以,默认 Exchange 适合简单投递,不适合承担可演进的业务路由设计。只要你的需求已经进入“多个业务订阅同一个事实”这个层级,就应该更认真地声明显式 Exchange 和独立 Queue。

一个简单够用的选型顺序

如果你不想一上来就陷入术语细节,可以按下面顺序判断:

  1. 订阅边界

    先判断需要几份业务订阅

    先区分“同一业务的多个实例”和“多个不同业务”。前者共享一份订阅,后者需要各自收到消息副本。

  2. Queue 拆分

    不同业务拆成独立 Queue

    库存、通知和审计分别拥有 Queue;每个业务内部再按吞吐需要部署多个竞争消费者。

  3. 路由语义

    再按订阅语义选择 Exchange

    精确值、层级模式、全部广播和多维属性,分别对应 Direct / Topic / Fanout / Headers

  4. 最小特例

    最后判断是否需要默认 Exchange

    只有发布方明确知道唯一目标 Queue,而且不需要演进成多订阅拓扑时,才把默认 Exchange 作为简单直达方案。

这个顺序的重点不是“选出最强的 Exchange”,而是先把业务边界想清楚:谁是一份独立订阅,谁只是同一份订阅下的多个实例。

一个常见的拓扑判断模板

order.created 这样的业务事件,很多团队最终会收敛到类似下面的思路:

REFERENCE TOPOLOGY

order.created 的推荐落点

订单服务只发布已经发生的业务事实,Exchange 负责复制,三个业务 Queue 分别承接自己的处理进度。

EXCHANGEorders.events
  • 库存订阅inventory.order-events
  • 通知订阅notification.order-events
  • 审计订阅audit.order-events
设计原则业务边界决定 Queue,订阅语义决定 Exchange

下一篇:RabbitMQ 消费控制:ack、nack、prefetch 与消息确认(四)