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 确认与死信队列]]
|
|||
|
|
- [[推拉结合消费模式]]
|