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 四种类型概览
| 类型 | 路由规则 | 适用场景 | 性能 |
|---|---|---|---|
| Direct | RoutingKey 完全等于 BindingKey | 精确路由到指定队列 | 最高 |
| Fanout | 忽略 RoutingKey,广播到所有绑定队列 | 一条消息多服务消费 | 最高 |
| Topic | RoutingKey 和 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.pay、order.pay.success、order |
单词的定义:RoutingKey 中用 . 分隔的每一段。比如 order.pay.success 有三个单词:order、pay、success。
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,不需要手动声明和绑定。
// 这行代码的效果:消息直接发到名为 "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 的代码