RabbitMQ知识梳理(2)

作者:没有四次元口袋的蓝胖

日期:2026-10-17

标签:RabbitMQ, 发布订阅, Fanout, Direct, Topic

RabbitMQ知识梳理(2)

写在前面

学完了 Hello World 和工作模型,你会发现它们都有一个共同点------消息只发给一个消费者。但实际业务中,我们经常需要"一条消息发给多个系统"或"按条件筛选消息"。

这就是 RabbitMQ 的三大消息分发模式:

  1. Fanout(发布订阅):广播给所有绑定的队列
  2. Direct(路由):精确匹配 routing key
  3. 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

七、写在最后

学习建议

  1. 先理解区别,再写代码

    Fanout、Direct、Topic 的区别是面试高频题,先搞清楚它们的应用场景,再动手写代码。

  2. Topic 是难点也是亮点

    通配符 * 和 # 的区别很多人搞不清,面试时能说清楚"order.* 匹配一级,order.# 匹配多级",会比只说"通配符"更加分。

  3. 实际项目中组合使用

    一个系统可能同时用到三种模式:Fanout 做订单广播,Direct 做任务路由,Topic 做日志收集。

高频面试题总结

问题 核心答案
Exchange有什么作用? 接收生产者消息,根据Binding规则转发到队列
Fanout和Direct有什么区别? Fanout广播到所有队列(忽略routing key);Direct精确匹配routing key
Topic的通配符*和#有什么区别? 匹配一个单词(order. 匹配 order.create);#匹配零个或多个单词(order.# 匹配 order.create.fast)
什么场景用Fanout? 消息需要发给所有系统(如订单广播)
什么场景用Direct? 消息只发给一个特定的系统(如订单创建只发给库存系统)
什么场景用Topic? 消息需要按类别筛选(如日志收集、只接收错误日志)
相关推荐
xianyuCcCcCCCcc16 小时前
Ceph 分布式存储:核心架构与集群部署
分布式·ceph·架构
真上帝的左手17 小时前
10. 软件设计&架构-分布式文件存储系统
分布式·架构·应用架构
叶总没有会18 小时前
RabbitMQ基础--java客户端
java·rabbitmq·java-rabbitmq
Thomas.Sir1 天前
第30课:分布式事务解决方案 & Seata最终一致性实战
分布式·spring cloud·seata
donk13141 天前
React Native for OpenHarmony 实战:三方库 react-native-circular-action-menu 的鸿蒙化适配指南
javascript·分布式·react native·react.js·wpf·harmonyos
Thomas.Sir1 天前
第31课:微服务消息驱动(RabbitMQ/RocketMQ)适配与解耦实战
微服务·rabbitmq·rocketmq
蓝胖的四次元口袋1 天前
RabbitMQ知识梳理(1)
分布式·rabbitmq
霸道流氓气质1 天前
Redisson BloomFilter:分布式位图实现、误判率调优与缓存穿透三级防护的 Spring Boot 生产级实战
spring boot·分布式·缓存
流烟默1 天前
ClickHouse 集群建表实战:从引擎选择到本地表 / 分布式表落地
分布式·clickhouse