171 lines
6.3 KiB
Markdown
171 lines
6.3 KiB
Markdown
---
|
||
tags: [mq/rabbitmq, exchange-routing, direct-exchange, fanout-exchange, topic-exchange, headers-exchange, delay-exchange]
|
||
create time: 2026-08-08 18:50
|
||
update time: 2026-08-08 18:50
|
||
---
|
||
|
||
# Exchange 路由机制
|
||
|
||
## 概述
|
||
|
||
Exchange(交换机)是 RabbitMQ 消息路由的核心组件,充当生产者与队列之间的中间层。生产者在发送消息时指定 Exchange 名称和 routing key,RabbitMQ 根据 Exchange 类型和路由规则将消息分发到一个或多个 Queue。掌握四种内置 Exchange 类型以及插件扩展的延迟交换能力,是设计可靠消息系统的基础。
|
||
|
||
## 核心原理
|
||
|
||
### 四种内置 Exchange 类型
|
||
|
||
#### Direct Exchange — 精确匹配
|
||
|
||
Direct Exchange 使用完全匹配原则:routing key 必须与 Queue 绑定到 Exchange 时的 binding key 完全一致,消息才会被投递。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant P as 生产者
|
||
participant E["Direct Exchange"]
|
||
participant Q1["Queue A (key: order.create)"]
|
||
participant Q2["Queue B (key: order.* )"]
|
||
participant Q3["Queue C (key: *.pay)"]
|
||
P->>E: 发送消息<br/>routing key: "order.create"
|
||
E->>Q1: 匹配成功,投递
|
||
Note over E,Q1: "order.create" == "order.create"
|
||
E->>Q2: 匹配失败
|
||
Note over E,Q2: "order.create" ne "order.*"
|
||
E->>Q3: 匹配失败
|
||
Note over E,Q3: "order.create" ne "*.pay"
|
||
```
|
||
|
||
关键特征:一对一或一对多投递(多个 Queue 绑定了相同的 binding key)。这是最常用也最可预测的路由方式。
|
||
|
||
#### Fanout Exchange — 广播模式
|
||
|
||
Fanout Exchange 忽略 routing key,将消息投递到所有绑定到该 Exchange 的 Queue。每次收到消息就是群发。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant P as 生产者
|
||
participant E["Fanout Exchange"]
|
||
participant Q1["订单队列"]
|
||
participant Q2["日志队列"]
|
||
participant Q3["通知队列"]
|
||
P->>E: 发送消息<br/>routing key: (任意值)
|
||
E->>Q1: 广播投递
|
||
E->>Q2: 广播投递
|
||
E->>Q3: 广播投递
|
||
Note right of E: 忽略 routing key<br/>全部投递
|
||
```
|
||
|
||
典型场景:事件广播、配置刷新、缓存失效通知。不需要关心消息内容被哪些消费者处理。
|
||
|
||
#### Topic Exchange — 通配符匹配
|
||
|
||
Topic Exchange 是最灵活的路由模式,支持通配符匹配:
|
||
- `*`(星号)匹配**恰好一个词**
|
||
- `#`(井号)匹配**零个或多个词**
|
||
|
||
词之间以点号 `.` 分隔。匹配规则示例:
|
||
|
||
| routing key | binding key | 是否匹配 |
|
||
|-------------|------------|---------|
|
||
| `quick.orange.fox` | `*.orange.*` | 是 |
|
||
| `quick.orange.fox` | `quick.*.fox` | 是 |
|
||
| `quick.brown.fox` | `*.orange.*` | 否 |
|
||
| `quick.orange.male.rabbit` | `#` | 是(匹配所有) |
|
||
| `lazy.orange.cat` | `lazy.#` | 是 |
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant P as 生产者
|
||
participant E["Topic Exchange"]
|
||
participant Q1["Queue: *.create"]
|
||
participant Q2["Queue: *.pay.*"]
|
||
participant Q3["Queue: #"]
|
||
P->>E: routing key: "order.create"
|
||
E->>Q1: 匹配 "*" -> 投递
|
||
E->>Q2: "order.pay.*" ne "*.pay.*" -> 不投递
|
||
E->>Q3: "#" -> 投递
|
||
P->>E: routing key: "order.pay.success"
|
||
E->>Q1: "*.create" ne "order.pay.success" -> 不投递
|
||
E->>Q2: 匹配 "*.pay.*" -> 投递
|
||
E->>Q3: "#" -> 投递
|
||
```
|
||
|
||
#### Headers Exchange — 基于属性匹配
|
||
|
||
Headers Exchange 忽略 routing key,通过检查消息的 headers 属性(key-value 对)来决定路由。可以设置 `match` 参数:`all`(所有 header 都匹配)或 `any`(任一 header 匹配即可)。
|
||
|
||
> [!WARNING]
|
||
> Headers Exchange 性能较差且灵活性不如 Topic Exchange,官方文档明确标注"不推荐在生产中使用"。应优先选择 Topic Exchange。
|
||
|
||
### 死信交换机的特殊绑定
|
||
|
||
Dead Letter Exchange(DLX)本质上也是一个普通 Exchange,只是通过队列属性间接触发。当队列中的消息满足以下条件之一时,会被重新路由到 DLX:
|
||
|
||
- 消息被 nack 且不重新入队(`requeue=false`)
|
||
- 消息 TTL 过期
|
||
- 队列长度限制已满
|
||
|
||
```yaml
|
||
# 队列声明时指定 DLX
|
||
queue:
|
||
name: "order.processing"
|
||
arguments:
|
||
x-dead-letter-exchange: "dead-letter-exchange"
|
||
x-dead-letter-routing-key: "order.dead"
|
||
```
|
||
|
||
### 延迟交换插件(x-delayed-message)
|
||
|
||
RabbitMQ 本身不提供延迟消息功能,需要安装 `rabbitmq_delayed_message_exchange` 插件后使用自定义 Exchange 类型 `x-delayed-message`。它通过消息的 `x-delay` header 控制投递时机。
|
||
|
||
```go
|
||
// Go 代码示例:声明延迟 Exchange
|
||
// (amqp.go 库简化写法)
|
||
args := amqp.Table{
|
||
"x-delayed-type": "direct", // 内部转发使用的 Exchange 类型
|
||
}
|
||
ch.ExchangeDeclare(
|
||
"delayed.exchange", // name
|
||
"x-delayed-message", // type
|
||
true, // durable
|
||
false, // auto-deleted
|
||
false, // internal
|
||
false, // no-wait
|
||
nil, // args
|
||
args, // 自定义参数
|
||
)
|
||
```
|
||
|
||
构建消息时设置 `x-delay` header(单位毫秒):
|
||
|
||
```go
|
||
// Go 代码示例:带延迟头的消息
|
||
msg := amqp.Publishing{
|
||
Body: []byte(`{"orderId":"123"}`),
|
||
Headers: amqp.Table{
|
||
"x-delay": uint32(60000), // 延迟 60 秒
|
||
},
|
||
}
|
||
```
|
||
|
||
> [!TIP]
|
||
> 面试常考点:RabbitMQ 原生不支持延迟消息。常见替代方案有定时任务轮询、Redis ZSet 定时弹出、或使用 Kafka 的分区时间排序特性。但在高可靠性场景中,x-delayed-message 插件是最直接可靠的方案。
|
||
|
||
## 实践场景
|
||
|
||
| 场景 | 推荐 Exchange | 原因 |
|
||
|------|--------------|-----|
|
||
| 订单创建通知下游 | Direct | 精确控制消息去向 |
|
||
| 全局缓存失效广播 | Fanout | 所有服务实例都需要收到 |
|
||
| 日志分级收集(info/error/warn) | Topic | 按级别前缀灵活路由 |
|
||
| 支付回调不同金额段分流 | Topic | 按 `amount.linux``amount.high` 等维度拆分 |
|
||
| 超时未支付取消订单 | x-delayed-message | 精确控制延迟投递时间 |
|
||
| 死信消息归档 | Direct + DLX 属性 | 标准化异常流程处理 |
|
||
|
||
> [!TIP]
|
||
> 秋招面试技巧:当被问到"如何设计一个支持多种消息类型的路由系统"时,优先考虑 Topic Exchange 的通配符能力,而非为每种类型创建独立的 Direct Exchange。Topic Exchange 天然支持层级化路由,运维上更简洁。
|
||
|
||
## 关联笔记
|
||
- [[消息持久化与可靠性投递]]
|
||
- [[ACK 确认与死信队列]]
|
||
- [[推拉结合消费模式]]
|