Skip to content

02 - 交换机类型与路由策略

面试高频度:⭐⭐⭐⭐⭐ 考查方式:对比题、场景设计题、选型题


一、Exchange 概述——为什么路由是 MQ 的核心能力?

1.1 Exchange 的本质

Exchange 是 RabbitMQ 的消息路由引擎。Producer 不直接发消息到 Queue,而是发到 Exchange,由 Exchange 根据规则分发到一个或多个 Queue。

为什么要把发送和路由分开?因为这样:

  • Producer 不需要知道 Queue 的存在——它只管把消息交给 Exchange
  • 一条消息可以被路由到多个 Queue——实现广播和多路分发
  • 路由规则可以动态调整——修改 Binding 不需要改 Producer 代码

Exchange 本身不存储消息——这是一个容易误解的点。消息要么成功路由到 Queue,要么被丢弃(或发到备用交换机)。如果 Exchange 找不到匹配的 Queue,消息就丢失了。所以开启 mandatory + Return 监听很重要(详见 **消息可靠性保障**)。

1.2 四种类型概览

类型路由规则适用场景性能
DirectRoutingKey 完全等于 BindingKey精确路由到指定队列最高
Fanout忽略 RoutingKey,广播到所有绑定队列一条消息多服务消费最高
TopicRoutingKey 和 BindingKey 做通配符匹配多维度分类路由
Headers根据消息 Header 属性匹配灵活但性能差较低

二、Direct Exchange——精确匹配

2.1 路由原理

Direct Exchange 的匹配规则最简单:消息的 RoutingKey 必须和 Queue 的 BindingKey 完全相等,才会路由。

消息的 RoutingKey = "order.pay"
                ↓ 匹配
Queue A 的 BindingKey = "order.pay"  ✅ 完全相等,路由成功
Queue B 的 BindingKey = "order.refund" ❌ 不相等,不路由
Queue C 的 BindingKey = "order.*"      ❌ Direct 不支持通配符

关键理解:Direct Exchange 是精确路由,不做任何模糊匹配。这既是它的优势(简单、高效、无歧义),也是它的局限(不够灵活)。

2.2 适用场景

  • 精确路由:不同类型的消息发到不同队列。比如订单创建、支付、退款各有独立的队列
  • 日志分级:error / warn / info 各走一条队列,分别由不同消费者处理

2.3 多个 Queue 绑定同一个 RoutingKey

一个容易忽略的特性:多个 Queue 可以绑定同一个 BindingKey。此时消息会被同时路由到这些 Queue——效果类似 Fanout,但限制在同一个 RoutingKey 下。这在需要"一路精确路由 + 按需复制"时很有用。


三、Fanout Exchange——广播

3.1 路由原理

Fanout Exchange 的路由规则最暴力:完全忽略消息的 RoutingKey,直接把消息复制到所有与该 Exchange 绑定的 Queue。

消息的 RoutingKey = "随便什么都行"
                ↓ Fanout 不看 RoutingKey
Queue A 绑定了 → ✅ 收到消息
Queue B 绑定了 → ✅ 收到消息
Queue C 绑定了 → ✅ 收到消息

为什么 Fanout 性能最好? 因为不需要做任何匹配计算——Direct 要做字符串比较,Topic 要做通配符匹配,Headers 要做 Map 遍历。Fanout 直接遍历绑定列表投递即可,O(n) 复杂度,n 是绑定的 Queue 数量。

3.2 适用场景

Fanout 的核心价值是一条消息被多个独立消费者同时处理: w

Fanout 配合**独占队列(exclusive queue)**使用是经典模式:

  • 每个消费者创建自己的独占队列,绑定到 Fanout Exchange
  • 独占队列只属于当前连接,连接断开自动删除
  • 这样每个消费者都能收到全量消息,且互不干扰

四、Topic Exchange——通配符匹配

4.1 路由原理

Topic Exchange 是最灵活的 Exchange 类型。它使用通配符匹配来决定路由:

通配符含义示例
*(星号)匹配恰好一个单词order.* 匹配 order.pay,不匹配 order.pay.success
#(井号)匹配零个或多个单词order.# 匹配 order.payorder.pay.successorder

单词的定义:RoutingKey 中用 . 分隔的每一段。比如 order.pay.success 有三个单词:orderpaysuccess

4.2 匹配规则详解

Binding Key          Routing Key           匹配?  原因
─────────────────────────────────────────────────────────────
order.*              order.pay             ✅     * 匹配 "pay"(恰好一个词)
order.*              order.pay.success     ❌     * 只能匹配一个词,"pay.success"是两个词
order.#              order.pay             ✅     # 匹配 "pay"(零或多个词)
order.#              order.pay.success     ✅     # 匹配 "pay.success"(两个词)
order.#              order                 ✅     # 匹配零个词(空)
*.*.success          order.pay.success     ✅     三段各自匹配
#.success            order.pay.success     ✅     # 匹配 "order.pay"
order.#.success      order.pay.success     ✅     # 匹配 "pay"(中间一个词)
order.#.success      order.pay.refund.success  ✅ # 匹配 "pay.refund"(中间两个词)

关键理解*# 的区别不在"匹配多少字符",而在"匹配多少个由点分隔的单词"。* 严格一个,# 零到多个。

4.3 适用场景

Topic Exchange 的核心价值是多维度分类路由。典型用法是把 RoutingKey 设计成层级结构:

格式:<业务域>.<操作类型>.<附加属性>

示例:
order.pay.success    → 匹配 "order.#" → 所有订单相关队列
order.pay.fail       → 匹配 "order.#" 和 "#.fail" → 订单队列 + 失败队列
user.register.cn     → 匹配 "user.#" 和 "*.*.cn" → 用户队列 + 中国区队列

这样不同的消费者可以按需订阅自己关心的维度,不需要在 Producer 端做分发逻辑。


五、Headers Exchange——属性匹配

5.1 路由原理

Headers Exchange 不使用 RoutingKey,而是根据消息的 Header 属性来匹配。

匹配模式:

  • x-match = all:消息 Header 必须包含 Binding 中指定的所有键值对
  • x-match = any:消息 Header 只需包含 Binding 中指定的任一键值对

5.2 为什么不常用?

缺点说明
性能差需要遍历消息的 Header Map 做匹配,比字符串比较慢
冗余大多数场景用 Topic Exchange 就能实现,Topic 的通配符匹配更直观
复杂度高需要维护 Header 的键值对,比 RoutingKey 不直观

Headers Exchange 的存在主要是为了满足"路由条件不适合用单个字符串表达"的极端场景(比如需要同时匹配多个属性),但实践中极少用到。


六、特殊 Exchange

6.1 Default Exchange("")

RabbitMQ 有一个默认交换机(名称为空字符串),类型为 Direct。它的特殊之处是:自动把消息路由到与 RoutingKey 同名的 Queue,不需要手动声明和绑定。

java
// 这行代码的效果:消息直接发到名为 "my_queue" 的 Queue
channel.basicPublish("", "my_queue", null, body);

Default Exchange 简化了最简单的点对点场景,但因为无法自定义路由规则,实际项目中通常会声明自己的 Exchange。

6.2 Alternate Exchange(备用交换机)

声明 Exchange 时通过 alternate-exchange 参数指定一个备用交换机。当消息无法路由到任何 Queue 时,自动转发到备用交换机,而不是静默丢弃。

这是生产端可靠性的重要保障——如果 RoutingKey 写错了或 Binding 配置有误,消息不会丢失,而是进入备用队列等待排查。


七、选型决策——怎么选 Exchange 类型?

你的消息需要路由到多少个 Queue?

├─ 只有一个 Queue → Direct(或 Default Exchange)

├─ 所有绑定 Queue 都要收到 → Fanout

├─ 需要根据消息内容/类型灵活路由 → Topic
│   │
│   └─ 只需要精确匹配 → Topic 也能做(退化为 Direct)
│      需要通配符 → Topic 最合适

└─ 需要同时匹配多个属性 → Headers(极少用)

经验法则:不确定的时候选 Topic——它最灵活,既能做精确匹配也能做通配符路由,性能损失可以忽略。


八、面试常见追问

Q1:一条消息能发到多个 Queue 吗?

可以。Fanout 和 Topic 都支持一条消息路由到多个 Queue。Direct 也可以(多个 Queue 绑定同一个 BindingKey),但通常不是这样用的。

Q2:如果消息无法路由到任何 Queue 会怎样?

默认情况消息会被静默丢弃。解决办法:① 开启 mandatory + Return 监听(生产端感知路由失败) ② 配置 Alternate Exchange(兜底接收)。两者可以同时使用。

Q3:Fanout 和 Topic 的性能差距大吗?

实际差距很小。Fanout 少了匹配计算,但这个计算的开销是纳秒级的。真正的性能瓶颈在消息的序列化/反序列化和网络传输,不在 Exchange 的路由匹配。所以选型时应该优先考虑业务需求,不需要为性能去选 Fanout


九、关联笔记

  • **RabbitMQ基础与核心概念** - AMQP 协议基础和核心组件
  • **消息可靠性保障** - Confirm 模式与 Return 机制
  • **SpringBoot整合RabbitMQ** - Spring 中声明 Exchange 的代码

Knowledge4J — Java 知识库