作者:没有四次元口袋的蓝胖
日期:2026-10-17
标签:RabbitMQ, 发布订阅, Fanout, Direct, Topic
RabbitMQ知识梳理(2)
写在前面
学完了 Hello World 和工作模型,你会发现它们都有一个共同点------消息只发给一个消费者。但实际业务中,我们经常需要"一条消息发给多个系统"或"按条件筛选消息"。
这就是 RabbitMQ 的三大消息分发模式:
- Fanout(发布订阅):广播给所有绑定的队列
- Direct(路由):精确匹配 routing key
- Topic(主题) :通配符匹配(
*和#)
这篇博客带你彻底搞懂这三种模式的区别和适用场景,让你在面试时能从业务需求出发,选择合适的消息分发方式。
一、核心概念:Exchange
1.1 为什么需要Exchange?
在 Hello World 模型中,生产者直接把消息发到队列。但在更复杂的场景中,我们需要根据规则把消息分发到不同的队列。
Exchange 的作用: 接收生产者的消息,根据绑定规则(Binding)转发到对应的队列。
生产者 → Exchange → (Binding规则) → Queue → 消费者
1.2 Exchange的四种类型
| 类型 | 规则 | 适用场景 |
|---|---|---|
| Fanout | 广播到所有绑定的队列 | 消息广播(如通知所有系统) |
| Direct | routing key 精确匹配 | 点对点路由(如订单系统只发给库存系统) |
| Topic | routing key 通配符匹配 | 多级路由(如 order.create 发给订单系统) |
| Headers | 根据消息头属性匹配 | 很少用,性能差 |
1.3 Binding(绑定)
Binding: Exchange 和 Queue 之间的关联关系,包含 routing key。
java
// 绑定示例
rabbitTemplate.convertAndSend("order.exchange", "order.create", orderId);
- order.exchange: Exchange 名称
- order.create: routing key
- 绑定规则决定消息发到哪个队列
二、Fanout模式:发布订阅
2.1 模型说明
Fanout(扇出) :把消息广播到所有绑定的队列,忽略 routing key。
┌→ Queue A → 消费者A(订单系统)
生产者 → Exchange ─┼→ Queue B → 消费者B(库存系统)
└→ Queue C → 消费者C(积分系统)
适用场景: 消息需要发给多个系统(如订单创建后,库存、积分、物流系统都要知道)。
2.2 代码实现
配置
java
@Configuration
public class FanoutConfig {
// 创建 Fanout Exchange
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("order.fanout.exchange");
}
// 创建队列A(订单系统)
@Bean
public Queue orderQueue() {
return new Queue("order.fanout.queue.a");
}
// 创建队列B(库存系统)
@Bean
public Queue inventoryQueue() {
return new Queue("order.fanout.queue.b");
}
// 创建队列C(积分系统)
@Bean
public Queue pointQueue() {
return new Queue("order.fanout.queue.c");
}
// 绑定队列到 Exchange(Fanout 忽略 routing key)
@Bean
public Binding bindingOrder(Queue orderQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(orderQueue).to(fanoutExchange);
}
@Bean
public Binding bindingInventory(Queue inventoryQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(inventoryQueue).to(fanoutExchange);
}
@Bean
public Binding bindingPoint(Queue pointQueue, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(pointQueue).to(fanoutExchange);
}
}
生产者
java
@Component
public class OrderProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void createOrder(Long orderId) {
// 发送消息到 Fanout Exchange(routing key 被忽略)
rabbitTemplate.convertAndSend("order.fanout.exchange", "", orderId);
System.out.println("订单创建: " + orderId);
}
}
消费者
java
@Component
public class OrderConsumers {
// 订单系统消费者
@RabbitListener(queues = "order.fanout.queue.a")
public void orderSystem(Long orderId) {
System.out.println("订单系统 收到订单: " + orderId);
// 记录订单日志
}
// 库存系统消费者
@RabbitListener(queues = "order.fanout.queue.b")
public void inventorySystem(Long orderId) {
System.out.println("库存系统 收到订单: " + orderId);
// 扣减库存
}
// 积分系统消费者
@RabbitListener(queues = "order.fanout.queue.c")
public void pointSystem(Long orderId) {
System.out.println("积分系统 收到订单: " + orderId);
// 增加积分
}
}
测试
java
@Test
public void testFanout() throws InterruptedException {
orderProducer.createOrder(1001L);
Thread.sleep(1000);
}
输出:
订单创建: 1001
订单系统 收到订单: 1001
库存系统 收到订单: 1001
积分系统 收到订单: 1001
特点:
- 一条消息,三个系统都收到
- routing key 被忽略(可以传空字符串)
- 新增系统只需要新建一个队列并绑定到 Exchange
2.3 实际场景:订单广播
用户下单 → 订单系统发送消息到 Fanout Exchange
├→ 库存系统:扣减库存
├→ 积分系统:增加积分
├→ 物流系统:准备发货
└→ 风控系统:风险评估
优势:
- 订单系统不需要知道有哪些下游系统
- 新增系统(如风控)只需要新建一个消费者,订单系统不用改
三、Direct模式:路由
3.1 模型说明
Direct(直连) :routing key 精确匹配,消息只发给匹配的队列。
┌→ Queue A (routing key: order.create) → 订单系统
生产者 → Exchange ──────┼→ Queue B (routing key: order.pay) → 支付系统
└→ Queue C (routing key: order.cancel) → 取消系统
适用场景: 消息只需要发给一个特定的系统(如订单创建只发给库存系统)。
3.2 代码实现
配置
java
@Configuration
public class DirectConfig {
// 创建 Direct Exchange
@Bean
public DirectExchange directExchange() {
return new DirectExchange("order.direct.exchange");
}
// 创建队列
@Bean
public Queue createOrderQueue() {
return new Queue("order.direct.queue.create");
}
@Bean
public Queue payOrderQueue() {
return new Queue("order.direct.queue.pay");
}
@Bean
public Queue cancelOrderQueue() {
return new Queue("order.direct.queue.cancel");
}
// 绑定队列到 Exchange(指定 routing key)
@Bean
public Binding bindingCreate(Queue createOrderQueue, DirectExchange directExchange) {
return BindingBuilder.bind(createOrderQueue)
.to(directExchange)
.with("order.create"); // routing key
}
@Bean
public Binding bindingPay(Queue payOrderQueue, DirectExchange directExchange) {
return BindingBuilder.bind(payOrderQueue)
.to(directExchange)
.with("order.pay");
}
@Bean
public Binding bindingCancel(Queue cancelOrderQueue, DirectExchange directExchange) {
return BindingBuilder.bind(cancelOrderQueue)
.to(directExchange)
.with("order.cancel");
}
}
生产者
java
@Component
public class OrderRouter {
@Autowired
private RabbitTemplate rabbitTemplate;
public void createOrder(Long orderId) {
// routing key = "order.create"
rabbitTemplate.convertAndSend("order.direct.exchange", "order.create", orderId);
System.out.println("订单创建: " + orderId);
}
public void payOrder(Long orderId) {
// routing key = "order.pay"
rabbitTemplate.convertAndSend("order.direct.exchange", "order.pay", orderId);
System.out.println("订单支付: " + orderId);
}
public void cancelOrder(Long orderId) {
// routing key = "order.cancel"
rabbitTemplate.convertAndSend("order.direct.exchange", "order.cancel", orderId);
System.out.println("订单取消: " + orderId);
}
}
消费者
java
@Component
public class OrderDirectConsumers {
@RabbitListener(queues = "order.direct.queue.create")
public void handleCreate(Long orderId) {
System.out.println("订单创建系统 收到订单: " + orderId);
}
@RabbitListener(queues = "order.direct.queue.pay")
public void handlePay(Long orderId) {
System.out.println("订单支付系统 收到订单: " + orderId);
}
@RabbitListener(queues = "order.direct.queue.cancel")
public void handleCancel(Long orderId) {
System.out.println("订单取消系统 收到订单: " + orderId);
}
}
测试
java
@Test
public void testDirect() throws InterruptedException {
orderRouter.createOrder(1001L); // 只发给创建系统
orderRouter.payOrder(1001L); // 只发给支付系统
orderRouter.cancelOrder(1002L); // 只发给取消系统
Thread.sleep(1000);
}
输出:
订单创建: 1001
订单创建系统 收到订单: 1001
订单支付: 1001
订单支付系统 收到订单: 1001
订单取消: 1002
订单取消系统 收到订单: 1002
特点:
- routing key 精确匹配
- 一个 routing key 只能绑定一个队列(一对一)
- 如果多个队列绑定同一个 routing key,退化为 Fanout
3.3 实际场景:订单路由
订单系统 → Direct Exchange
├→ routing key: order.create → 库存系统
├→ routing key: order.pay → 支付系统
└→ routing key: order.cancel → 取消系统
优势:
- 精确控制消息路由
- 不同类型的订单事件发给不同的系统
四、Topic模式:主题
4.1 模型说明
Topic(主题) :routing key 使用通配符匹配,支持多级路由。
routing key 格式: 单词1.单词2.单词3(用 . 分隔)
通配符:
-
*:匹配一个单词 -
#:匹配零个或多个单词┌→ Queue A (binding: order.*) → 匹配 order.create, order.pay生产者 → Exchange ──────┼→ Queue B (binding: order.#) → 匹配 order.create, order.create.fast
└→ Queue C (binding: #.error) → 匹配 order.error, pay.error
适用场景: 消息需要按类别筛选(如所有订单事件、所有错误日志)。
4.2 代码实现
配置
java
@Configuration
public class TopicConfig {
// 创建 Topic Exchange
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("log.topic.exchange");
}
// 队列A:只接收 order.* 的日志(一级)
@Bean
public Queue orderOneLevelQueue() {
return new Queue("log.topic.queue.order.one");
}
// 队列B:接收 order.* 的所有日志(多级)
@Bean
public Queue orderAllLevelQueue() {
return new Queue("log.topic.queue.order.all");
}
// 队列C:接收所有错误日志
@Bean
public Queue errorQueue() {
return new Queue("log.topic.queue.error");
}
// 绑定规则
@Bean
public Binding bindingOrderOne(Queue orderOneLevelQueue, TopicExchange topicExchange) {
return BindingBuilder.bind(orderOneLevelQueue)
.to(topicExchange)
.with("order.*"); // 只匹配一级(如 order.create)
}
@Bean
public Binding bindingOrderAll(Queue orderAllLevelQueue, TopicExchange topicExchange) {
return BindingBuilder.bind(orderAllLevelQueue)
.to(topicExchange)
.with("order.#"); // 匹配多级(如 order.create, order.create.fast)
}
@Bean
public Binding bindingError(Queue errorQueue, TopicExchange topicExchange) {
return BindingBuilder.bind(errorQueue)
.to(topicExchange)
.with("#.error"); // 匹配所有以 .error 结尾的
}
}
生产者
java
@Component
public class LogProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendLog(String routingKey, String message) {
rabbitTemplate.convertAndSend("log.topic.exchange", routingKey, message);
System.out.println("发送日志 [" + routingKey + "]: " + message);
}
}
消费者
java
@Component
public class LogConsumers {
@RabbitListener(queues = "log.topic.queue.order.one")
public void orderOneLevel(String message) {
System.out.println("一级订单日志: " + message);
}
@RabbitListener(queues = "log.topic.queue.order.all")
public void orderAllLevel(String message) {
System.out.println("所有订单日志: " + message);
}
@RabbitListener(queues = "log.topic.queue.error")
public void errorLog(String message) {
System.out.println("错误日志: " + message);
}
}
测试
java
@Test
public void testTopic() throws InterruptedException {
// 一级订单日志
logProducer.sendLog("order.create", "订单创建");
logProducer.sendLog("order.pay", "订单支付");
// 多级订单日志
logProducer.sendLog("order.create.fast", "快速订单创建");
// 错误日志
logProducer.sendLog("order.error", "订单处理失败");
logProducer.sendLog("pay.error", "支付失败");
// 其他日志
logProducer.sendLog("user.login", "用户登录");
Thread.sleep(1000);
}
输出:
发送日志 [order.create]: 订单创建
一级订单日志: 订单创建 ← order.* 匹配
所有订单日志: 订单创建 ← order.# 匹配
发送日志 [order.pay]: 订单支付
一级订单日志: 订单支付 ← order.* 匹配
所有订单日志: 订单支付 ← order.# 匹配
发送日志 [order.create.fast]: 快速订单创建
所有订单日志: 快速订单创建 ← order.# 匹配(order.* 不匹配,因为是两级)
发送日志 [order.error]: 订单处理失败
一级订单日志: 订单处理失败 ← order.* 匹配
所有订单日志: 订单处理失败 ← order.# 匹配
错误日志: 订单处理失败 ← #.error 匹配
发送日志 [pay.error]: 支付失败
错误日志: 支付失败 ← #.error 匹配
发送日志 [user.login]: 用户登录
(没有消费者接收,消息被丢弃)
4.3 通配符规则总结
| Binding Key | 匹配的 Routing Key | 不匹配的 Routing Key |
|---|---|---|
order.* |
order.create, order.pay |
order.create.fast(两级), user.login |
order.# |
order.create, order.create.fast, order.a.b.c |
user.login |
#.error |
order.error, pay.error, a.b.error |
order.create, error.order |
*.* |
order.create, user.login |
order.create.fast(三级) |
4.4 实际场景:日志收集
应用系统 → Topic Exchange
├→ routing key: order.* → 订单日志系统(一级)
├→ routing key: order.# → 订单归档系统(多级)
├→ routing key: #.error → 错误告警系统
└→ routing key: # → 全量日志系统
优势:
- 灵活的消息路由
- 不同系统按需订阅日志
五、三种模式对比
| 特性 | Fanout | Direct | Topic |
|---|---|---|---|
| 规则 | 广播(忽略 routing key) | 精确匹配 | 通配符匹配 |
| 适用场景 | 消息广播(如通知所有系统) | 点对点路由 | 多级路由、分类订阅 |
| 性能 | 最高(不需要匹配) | 高(精确匹配) | 中(通配符匹配) |
| 灵活性 | 低(全收) | 中(精确) | 高(通配符) |
| 典型应用 | 订单广播、公告通知 | 订单路由、任务分发 | 日志收集、消息分类 |
选择建议:
- Fanout: 消息需要发给所有系统(如订单创建后,所有下游系统都要知道)
- Direct: 消息只发给一个特定的系统(如订单创建只发给库存系统)
- Topic: 消息需要按类别筛选(如只接收订单相关的日志,或只接收错误日志)
六、思维导图速览
RabbitMQ三大消息分发模式
├── Exchange(消息交换机)
│ ├── 接收生产者消息
│ ├── 根据 Binding 规则转发到队列
│ └── 四种类型:Fanout / Direct / Topic / Headers
├── Fanout(发布订阅)
│ ├── 规则:广播到所有绑定的队列,忽略 routing key
│ ├── 适用:消息广播(如订单通知所有系统)
│ └── 代码:BindingBuilder.bind(queue).to(fanoutExchange)
├── Direct(路由)
│ ├── 规则:routing key 精确匹配
│ ├── 适用:点对点路由(如订单创建只发给库存系统)
│ └── 代码:BindingBuilder.bind(queue).to(directExchange).with("order.create")
├── Topic(主题)
│ ├── 规则:routing key 通配符匹配
│ │ ├── *:匹配一个单词
│ │ └── #:匹配零个或多个单词
│ ├── 适用:多级路由、分类订阅(如日志收集)
│ └── 代码:BindingBuilder.bind(queue).to(topicExchange).with("order.*")
└── 选择建议
├── 广播所有系统 → Fanout
├── 精确路由到一个系统 → Direct
└── 按类别筛选 → Topic
七、写在最后
学习建议
-
先理解区别,再写代码
Fanout、Direct、Topic 的区别是面试高频题,先搞清楚它们的应用场景,再动手写代码。
-
Topic 是难点也是亮点
通配符
*和#的区别很多人搞不清,面试时能说清楚"order.* 匹配一级,order.# 匹配多级",会比只说"通配符"更加分。 -
实际项目中组合使用
一个系统可能同时用到三种模式:Fanout 做订单广播,Direct 做任务路由,Topic 做日志收集。
高频面试题总结
| 问题 | 核心答案 |
|---|---|
| Exchange有什么作用? | 接收生产者消息,根据Binding规则转发到队列 |
| Fanout和Direct有什么区别? | Fanout广播到所有队列(忽略routing key);Direct精确匹配routing key |
| Topic的通配符*和#有什么区别? | 匹配一个单词(order. 匹配 order.create);#匹配零个或多个单词(order.# 匹配 order.create.fast) |
| 什么场景用Fanout? | 消息需要发给所有系统(如订单广播) |
| 什么场景用Direct? | 消息只发给一个特定的系统(如订单创建只发给库存系统) |
| 什么场景用Topic? | 消息需要按类别筛选(如日志收集、只接收错误日志) |