[RabbitMQ]应用知识点总结

🌟 导言

本文围绕 RabbitMQ 在实际开发中的核心应用方式 展开,系统梳理了消息通信模型、交换机路由机制、常见工作模式、可靠性保障手段以及 Spring Boot 整合 RabbitMQ 的基本写法。读完后,可以快速建立一套从 概念理解代码落地 的 RabbitMQ 知识框架。

🗺️ 知识脑图/架构

  • RabbitMQ 核心角色
    • Producer:生产者,负责发送消息
    • Consumer:消费者,负责处理消息
    • Queue:队列,负责存储消息
    • Exchange:交换机,负责路由消息
    • RoutingKey:生产者发送消息时携带的路由键
    • BindingKey:队列绑定交换机时指定的匹配规则
  • RabbitMQ 常见工作模式
    • Simple:简单模式
    • Work Queues:工作队列模式
    • Publish/Subscribe:发布订阅模式
    • Routing:路由模式
    • Topics:通配符模式
    • RPC:远程调用模式
    • Publisher Confirms:发布确认模式
  • 路由与交换机机制
    • Fanout Exchange:广播
    • Direct Exchange:精确匹配
    • Topic Exchange:通配匹配
    • Headers Exchange:基于消息头匹配
  • 消息可靠性保障
    • 持久化:队列/交换机/消息持久化
    • 消费者确认:自动 ACK 与手动 ACK
    • QoS / prefetch:限制未确认消息数量
    • mandatory:处理不可路由消息
    • Publisher Confirms:保证生产者发送侧可靠
  • Spring Boot 整合 RabbitMQ
    • RabbitTemplate 发送消息
    • @RabbitListener 监听消息
    • QueueBuilder / ExchangeBuilder / BindingBuilder 声明组件

🚀 核心知识点详解

1. RabbitMQ 到底解决了什么问题

1.1 什么是 RabbitMQ

RabbitMQ 是一个实现了 AMQP 协议的消息中间件,主要作用是在系统之间进行 异步通信、应用解耦、流量削峰、任务分发

它的本质不是"存数据",而是"在系统之间传递消息并按规则分发消息"。

1.2 为什么要用 RabbitMQ

在业务系统中,如果所有模块都采用同步调用,会出现几个典型问题:

  1. 系统耦合度高,一个服务挂掉会影响整条调用链。
  2. 峰值流量时容易把下游服务压垮。
  3. 某些任务并不需要立刻完成,更适合异步处理。

RabbitMQ 的价值正体现在这些地方:

痛点 RabbitMQ 的解决方式
服务强耦合 通过消息队列让系统解耦
突发高并发 通过队列缓存消息,起到削峰作用
耗时任务阻塞主流程 改为异步消费,提高响应速度
一条消息需要多方处理 通过交换机广播或路由分发

核心理解:RabbitMQ 不是为了替代数据库,而是为了让"消息的发送"和"消息的处理"解耦。

2. RabbitMQ 的核心组成与消息流转

2.1 核心角色

角色 作用 关键点
Producer 发送消息 只负责投递,不负责处理
Consumer 消费消息 负责业务逻辑执行
Queue 存储消息 消息真正落地的地方
Exchange 路由消息 决定消息发往哪些队列
RoutingKey 发送路由键 生产者告诉交换机如何分发
BindingKey 绑定规则 队列告诉交换机接收哪些消息

2.2 消息流转总过程

  1. Producer 将消息发送给 Exchange
  2. Exchange 根据交换机类型和 RoutingKey 进行路由。
  3. 满足规则的队列通过 BindingKey 接收消息。
  4. Consumer 从队列中拉取或监听消息并处理。

#mermaid-svg-V3dqpjoy2LDTJhku{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V3dqpjoy2LDTJhku .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V3dqpjoy2LDTJhku .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V3dqpjoy2LDTJhku .error-icon{fill:#552222;}#mermaid-svg-V3dqpjoy2LDTJhku .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V3dqpjoy2LDTJhku .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V3dqpjoy2LDTJhku .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V3dqpjoy2LDTJhku .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V3dqpjoy2LDTJhku .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V3dqpjoy2LDTJhku .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V3dqpjoy2LDTJhku .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V3dqpjoy2LDTJhku .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V3dqpjoy2LDTJhku .marker.cross{stroke:#333333;}#mermaid-svg-V3dqpjoy2LDTJhku svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V3dqpjoy2LDTJhku p{margin:0;}#mermaid-svg-V3dqpjoy2LDTJhku .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V3dqpjoy2LDTJhku .cluster-label text{fill:#333;}#mermaid-svg-V3dqpjoy2LDTJhku .cluster-label span{color:#333;}#mermaid-svg-V3dqpjoy2LDTJhku .cluster-label span p{background-color:transparent;}#mermaid-svg-V3dqpjoy2LDTJhku .label text,#mermaid-svg-V3dqpjoy2LDTJhku span{fill:#333;color:#333;}#mermaid-svg-V3dqpjoy2LDTJhku .node rect,#mermaid-svg-V3dqpjoy2LDTJhku .node circle,#mermaid-svg-V3dqpjoy2LDTJhku .node ellipse,#mermaid-svg-V3dqpjoy2LDTJhku .node polygon,#mermaid-svg-V3dqpjoy2LDTJhku .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V3dqpjoy2LDTJhku .rough-node .label text,#mermaid-svg-V3dqpjoy2LDTJhku .node .label text,#mermaid-svg-V3dqpjoy2LDTJhku .image-shape .label,#mermaid-svg-V3dqpjoy2LDTJhku .icon-shape .label{text-anchor:middle;}#mermaid-svg-V3dqpjoy2LDTJhku .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V3dqpjoy2LDTJhku .rough-node .label,#mermaid-svg-V3dqpjoy2LDTJhku .node .label,#mermaid-svg-V3dqpjoy2LDTJhku .image-shape .label,#mermaid-svg-V3dqpjoy2LDTJhku .icon-shape .label{text-align:center;}#mermaid-svg-V3dqpjoy2LDTJhku .node.clickable{cursor:pointer;}#mermaid-svg-V3dqpjoy2LDTJhku .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V3dqpjoy2LDTJhku .arrowheadPath{fill:#333333;}#mermaid-svg-V3dqpjoy2LDTJhku .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V3dqpjoy2LDTJhku .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V3dqpjoy2LDTJhku .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V3dqpjoy2LDTJhku .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V3dqpjoy2LDTJhku .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V3dqpjoy2LDTJhku .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V3dqpjoy2LDTJhku .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V3dqpjoy2LDTJhku .cluster text{fill:#333;}#mermaid-svg-V3dqpjoy2LDTJhku .cluster span{color:#333;}#mermaid-svg-V3dqpjoy2LDTJhku div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V3dqpjoy2LDTJhku .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V3dqpjoy2LDTJhku rect.text{fill:none;stroke-width:0;}#mermaid-svg-V3dqpjoy2LDTJhku .icon-shape,#mermaid-svg-V3dqpjoy2LDTJhku .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V3dqpjoy2LDTJhku .icon-shape p,#mermaid-svg-V3dqpjoy2LDTJhku .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V3dqpjoy2LDTJhku .icon-shape .label rect,#mermaid-svg-V3dqpjoy2LDTJhku .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V3dqpjoy2LDTJhku .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V3dqpjoy2LDTJhku .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V3dqpjoy2LDTJhku :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} message + RoutingKey
BindingKey 匹配
BindingKey 匹配
Producer

生产者
Exchange

交换机
Queue 1
Queue 2
Consumer 1
Consumer 2

关键结论 :在 RabbitMQ 中,生产者通常不是直接把消息发给队列,而是先发给 Exchange,再由交换机决定消息进入哪个队列。

2.3 RoutingKey 与 BindingKey 的区别

对比项 RoutingKey BindingKey
出现阶段 发送消息时 绑定队列时
谁来指定 生产者 队列与交换机的绑定关系
作用 告诉交换机如何路由 告诉交换机队列接收什么消息

可以这样记:生产者发消息时用 RoutingKey,队列绑定交换机时用 BindingKey。

3. 七种工作模式总览

3.1 模式对比表

模式 是否使用交换机 消息分发特点 典型场景
Simple 默认交换机 一个生产者对应一个消费者 最基础的点对点通信
Work Queues 默认交换机 多个消费者竞争同一队列消息 异步任务、短信发送
Publish/Subscribe Fanout 广播到所有绑定队列 通知、订阅、广播
Routing Direct 按精确路由键分发 按日志级别分类
Topics Topic 按通配符规则匹配 复杂业务分类分发
RPC 队列 + 回调队列 请求响应式通信 远程调用
Publisher Confirms 任意 保证生产者发送可靠 金融、订单等高可靠场景

4. Simple 与 Work Queues

4.1 Simple 简单模式

Simple 是最基础的消息模型:一个生产者、一个队列、一个消费者

适用场景:

  1. 入门学习。
  2. 单一消费者处理消息。
  3. 对消息分发要求不复杂的简单业务。

4.2 Work Queues 工作队列模式

Work Queues 可以理解为 Simple 模式的增强版:多个消费者共同消费一个队列中的消息

其核心价值是把大量任务分摊给多个消费者处理,提升吞吐量。
#mermaid-svg-9RpMDbpGH7gfzDyy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9RpMDbpGH7gfzDyy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9RpMDbpGH7gfzDyy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9RpMDbpGH7gfzDyy .error-icon{fill:#552222;}#mermaid-svg-9RpMDbpGH7gfzDyy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9RpMDbpGH7gfzDyy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9RpMDbpGH7gfzDyy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9RpMDbpGH7gfzDyy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9RpMDbpGH7gfzDyy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9RpMDbpGH7gfzDyy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9RpMDbpGH7gfzDyy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9RpMDbpGH7gfzDyy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9RpMDbpGH7gfzDyy .marker.cross{stroke:#333333;}#mermaid-svg-9RpMDbpGH7gfzDyy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9RpMDbpGH7gfzDyy p{margin:0;}#mermaid-svg-9RpMDbpGH7gfzDyy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9RpMDbpGH7gfzDyy .cluster-label text{fill:#333;}#mermaid-svg-9RpMDbpGH7gfzDyy .cluster-label span{color:#333;}#mermaid-svg-9RpMDbpGH7gfzDyy .cluster-label span p{background-color:transparent;}#mermaid-svg-9RpMDbpGH7gfzDyy .label text,#mermaid-svg-9RpMDbpGH7gfzDyy span{fill:#333;color:#333;}#mermaid-svg-9RpMDbpGH7gfzDyy .node rect,#mermaid-svg-9RpMDbpGH7gfzDyy .node circle,#mermaid-svg-9RpMDbpGH7gfzDyy .node ellipse,#mermaid-svg-9RpMDbpGH7gfzDyy .node polygon,#mermaid-svg-9RpMDbpGH7gfzDyy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9RpMDbpGH7gfzDyy .rough-node .label text,#mermaid-svg-9RpMDbpGH7gfzDyy .node .label text,#mermaid-svg-9RpMDbpGH7gfzDyy .image-shape .label,#mermaid-svg-9RpMDbpGH7gfzDyy .icon-shape .label{text-anchor:middle;}#mermaid-svg-9RpMDbpGH7gfzDyy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9RpMDbpGH7gfzDyy .rough-node .label,#mermaid-svg-9RpMDbpGH7gfzDyy .node .label,#mermaid-svg-9RpMDbpGH7gfzDyy .image-shape .label,#mermaid-svg-9RpMDbpGH7gfzDyy .icon-shape .label{text-align:center;}#mermaid-svg-9RpMDbpGH7gfzDyy .node.clickable{cursor:pointer;}#mermaid-svg-9RpMDbpGH7gfzDyy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9RpMDbpGH7gfzDyy .arrowheadPath{fill:#333333;}#mermaid-svg-9RpMDbpGH7gfzDyy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9RpMDbpGH7gfzDyy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9RpMDbpGH7gfzDyy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9RpMDbpGH7gfzDyy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9RpMDbpGH7gfzDyy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9RpMDbpGH7gfzDyy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9RpMDbpGH7gfzDyy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9RpMDbpGH7gfzDyy .cluster text{fill:#333;}#mermaid-svg-9RpMDbpGH7gfzDyy .cluster span{color:#333;}#mermaid-svg-9RpMDbpGH7gfzDyy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9RpMDbpGH7gfzDyy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9RpMDbpGH7gfzDyy rect.text{fill:none;stroke-width:0;}#mermaid-svg-9RpMDbpGH7gfzDyy .icon-shape,#mermaid-svg-9RpMDbpGH7gfzDyy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9RpMDbpGH7gfzDyy .icon-shape p,#mermaid-svg-9RpMDbpGH7gfzDyy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9RpMDbpGH7gfzDyy .icon-shape .label rect,#mermaid-svg-9RpMDbpGH7gfzDyy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9RpMDbpGH7gfzDyy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9RpMDbpGH7gfzDyy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9RpMDbpGH7gfzDyy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Producer
Queue

work_queues
Consumer 1
Consumer 2

4.3 为什么 Work Queues 很重要

它解决的是"任务堆积 "和"单消费者处理不过来"的问题。

比如下单成功后,需要发送短信、邮件、推送等任务,这些任务可以先进入队列,由多个消费者竞争消费。

4.4 怎么用

  1. 创建连接和信道。
  2. 声明队列 queueDeclare
  3. 生产者通过 basicPublish 发消息。
  4. 消费者通过 basicConsume 监听消息。
java 复制代码
ConnectionFactory factory = new ConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();

channel.queueDeclare("work_queues", true, false, false, null);

for (int i = 0; i < 10; i++) {
    String msg = "Hello World" + i;
    channel.basicPublish("", "work_queues", null, msg.getBytes());
}

核心特点:同一个队列中的同一条消息,只会被其中一个消费者消费,不会被多个消费者重复消费。

5. Publish/Subscribe:发布订阅模式

5.1 是什么

发布订阅模式的本质是:生产者发送一条消息,多个消费者都能收到同一份消息

它依赖 Fanout Exchange 实现广播。

5.2 为什么需要交换机

如果没有交换机,生产者只能把消息发到某个具体队列,无法灵活地让多方同时收到消息。

Fanout Exchange 会把消息复制后分发给所有绑定的队列。
#mermaid-svg-IoiM0UMDKX485aaQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IoiM0UMDKX485aaQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IoiM0UMDKX485aaQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IoiM0UMDKX485aaQ .error-icon{fill:#552222;}#mermaid-svg-IoiM0UMDKX485aaQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IoiM0UMDKX485aaQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IoiM0UMDKX485aaQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IoiM0UMDKX485aaQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IoiM0UMDKX485aaQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IoiM0UMDKX485aaQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IoiM0UMDKX485aaQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IoiM0UMDKX485aaQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IoiM0UMDKX485aaQ .marker.cross{stroke:#333333;}#mermaid-svg-IoiM0UMDKX485aaQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IoiM0UMDKX485aaQ p{margin:0;}#mermaid-svg-IoiM0UMDKX485aaQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IoiM0UMDKX485aaQ .cluster-label text{fill:#333;}#mermaid-svg-IoiM0UMDKX485aaQ .cluster-label span{color:#333;}#mermaid-svg-IoiM0UMDKX485aaQ .cluster-label span p{background-color:transparent;}#mermaid-svg-IoiM0UMDKX485aaQ .label text,#mermaid-svg-IoiM0UMDKX485aaQ span{fill:#333;color:#333;}#mermaid-svg-IoiM0UMDKX485aaQ .node rect,#mermaid-svg-IoiM0UMDKX485aaQ .node circle,#mermaid-svg-IoiM0UMDKX485aaQ .node ellipse,#mermaid-svg-IoiM0UMDKX485aaQ .node polygon,#mermaid-svg-IoiM0UMDKX485aaQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IoiM0UMDKX485aaQ .rough-node .label text,#mermaid-svg-IoiM0UMDKX485aaQ .node .label text,#mermaid-svg-IoiM0UMDKX485aaQ .image-shape .label,#mermaid-svg-IoiM0UMDKX485aaQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-IoiM0UMDKX485aaQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IoiM0UMDKX485aaQ .rough-node .label,#mermaid-svg-IoiM0UMDKX485aaQ .node .label,#mermaid-svg-IoiM0UMDKX485aaQ .image-shape .label,#mermaid-svg-IoiM0UMDKX485aaQ .icon-shape .label{text-align:center;}#mermaid-svg-IoiM0UMDKX485aaQ .node.clickable{cursor:pointer;}#mermaid-svg-IoiM0UMDKX485aaQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IoiM0UMDKX485aaQ .arrowheadPath{fill:#333333;}#mermaid-svg-IoiM0UMDKX485aaQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IoiM0UMDKX485aaQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IoiM0UMDKX485aaQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IoiM0UMDKX485aaQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IoiM0UMDKX485aaQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IoiM0UMDKX485aaQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IoiM0UMDKX485aaQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IoiM0UMDKX485aaQ .cluster text{fill:#333;}#mermaid-svg-IoiM0UMDKX485aaQ .cluster span{color:#333;}#mermaid-svg-IoiM0UMDKX485aaQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IoiM0UMDKX485aaQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IoiM0UMDKX485aaQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-IoiM0UMDKX485aaQ .icon-shape,#mermaid-svg-IoiM0UMDKX485aaQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IoiM0UMDKX485aaQ .icon-shape p,#mermaid-svg-IoiM0UMDKX485aaQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IoiM0UMDKX485aaQ .icon-shape .label rect,#mermaid-svg-IoiM0UMDKX485aaQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IoiM0UMDKX485aaQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IoiM0UMDKX485aaQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IoiM0UMDKX485aaQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} message
Producer
Fanout Exchange
Queue 1
Queue 2
Consumer 1
Consumer 2

5.3 怎么用

核心步骤如下:

  1. 声明 Fanout 类型交换机。
  2. 声明多个队列。
  3. 把多个队列绑定到同一个交换机。
  4. 发送消息时 routingKey 通常传空字符串。
java 复制代码
channel.exchangeDeclare("test_fanout", BuiltinExchangeType.FANOUT, true, false, false, null);
channel.queueDeclare("fanout_queue1", true, false, false, null);
channel.queueDeclare("fanout_queue2", true, false, false, null);

channel.queueBind("fanout_queue1", "test_fanout", "");
channel.queueBind("fanout_queue2", "test_fanout", "");

channel.basicPublish("test_fanout", "", null, "hello fanout".getBytes());

适用场景:通知广播、实时消息推送、事件同步给多个系统。

6. Routing:路由模式

6.1 是什么

Routing 模式 是发布订阅模式的升级版,使用 Direct Exchange 按照 RoutingKey 精确匹配 把消息发送到指定队列。

6.2 为什么需要 Routing

Fanout 是"无脑广播",所有绑定队列都会收到消息;但有些业务只希望消息分给特定消费者。

例如日志系统中:

  1. error 日志进入错误处理队列。
  2. info 日志进入信息归档队列。
  3. warning 日志进入监控告警队列。

6.3 路由机制图

#mermaid-svg-4prKrzwqz2bwWrSt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4prKrzwqz2bwWrSt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4prKrzwqz2bwWrSt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4prKrzwqz2bwWrSt .error-icon{fill:#552222;}#mermaid-svg-4prKrzwqz2bwWrSt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4prKrzwqz2bwWrSt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4prKrzwqz2bwWrSt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4prKrzwqz2bwWrSt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4prKrzwqz2bwWrSt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4prKrzwqz2bwWrSt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4prKrzwqz2bwWrSt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4prKrzwqz2bwWrSt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4prKrzwqz2bwWrSt .marker.cross{stroke:#333333;}#mermaid-svg-4prKrzwqz2bwWrSt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4prKrzwqz2bwWrSt p{margin:0;}#mermaid-svg-4prKrzwqz2bwWrSt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4prKrzwqz2bwWrSt .cluster-label text{fill:#333;}#mermaid-svg-4prKrzwqz2bwWrSt .cluster-label span{color:#333;}#mermaid-svg-4prKrzwqz2bwWrSt .cluster-label span p{background-color:transparent;}#mermaid-svg-4prKrzwqz2bwWrSt .label text,#mermaid-svg-4prKrzwqz2bwWrSt span{fill:#333;color:#333;}#mermaid-svg-4prKrzwqz2bwWrSt .node rect,#mermaid-svg-4prKrzwqz2bwWrSt .node circle,#mermaid-svg-4prKrzwqz2bwWrSt .node ellipse,#mermaid-svg-4prKrzwqz2bwWrSt .node polygon,#mermaid-svg-4prKrzwqz2bwWrSt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4prKrzwqz2bwWrSt .rough-node .label text,#mermaid-svg-4prKrzwqz2bwWrSt .node .label text,#mermaid-svg-4prKrzwqz2bwWrSt .image-shape .label,#mermaid-svg-4prKrzwqz2bwWrSt .icon-shape .label{text-anchor:middle;}#mermaid-svg-4prKrzwqz2bwWrSt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4prKrzwqz2bwWrSt .rough-node .label,#mermaid-svg-4prKrzwqz2bwWrSt .node .label,#mermaid-svg-4prKrzwqz2bwWrSt .image-shape .label,#mermaid-svg-4prKrzwqz2bwWrSt .icon-shape .label{text-align:center;}#mermaid-svg-4prKrzwqz2bwWrSt .node.clickable{cursor:pointer;}#mermaid-svg-4prKrzwqz2bwWrSt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4prKrzwqz2bwWrSt .arrowheadPath{fill:#333333;}#mermaid-svg-4prKrzwqz2bwWrSt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4prKrzwqz2bwWrSt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4prKrzwqz2bwWrSt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4prKrzwqz2bwWrSt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4prKrzwqz2bwWrSt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4prKrzwqz2bwWrSt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4prKrzwqz2bwWrSt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4prKrzwqz2bwWrSt .cluster text{fill:#333;}#mermaid-svg-4prKrzwqz2bwWrSt .cluster span{color:#333;}#mermaid-svg-4prKrzwqz2bwWrSt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4prKrzwqz2bwWrSt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4prKrzwqz2bwWrSt rect.text{fill:none;stroke-width:0;}#mermaid-svg-4prKrzwqz2bwWrSt .icon-shape,#mermaid-svg-4prKrzwqz2bwWrSt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4prKrzwqz2bwWrSt .icon-shape p,#mermaid-svg-4prKrzwqz2bwWrSt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4prKrzwqz2bwWrSt .icon-shape .label rect,#mermaid-svg-4prKrzwqz2bwWrSt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4prKrzwqz2bwWrSt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4prKrzwqz2bwWrSt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4prKrzwqz2bwWrSt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RoutingKey = orange
BindingKey = orange
BindingKey = black / green
Producer
Direct Exchange
Queue 1
Queue 2
Consumer 1
Consumer 2

6.4 怎么用

java 复制代码
channel.exchangeDeclare("test_direct", BuiltinExchangeType.DIRECT, true, false, false, null);

channel.queueDeclare("direct_queue1", true, false, false, null);
channel.queueDeclare("direct_queue2", true, false, false, null);

channel.queueBind("direct_queue1", "test_direct", "orange");
channel.queueBind("direct_queue2", "test_direct", "black");
channel.queueBind("direct_queue2", "test_direct", "green");

channel.basicPublish("test_direct", "orange", null, "hello direct, I am orange".getBytes());
channel.basicPublish("test_direct", "black", null, "hello direct, I am black".getBytes());
channel.basicPublish("test_direct", "green", null, "hello direct, I am green".getBytes());

核心规则 :Direct 模式下,只有 RoutingKey 与 BindingKey 完全一致,消息才会进入对应队列。

7. Topics:通配符模式

7.1 是什么

Topics 模式 可以理解为比 Routing 更灵活的路由模型,使用 Topic Exchange,支持通配符匹配。

7.2 通配符规则

通配符 含义
* 匹配一个单词
# 匹配零个或多个单词

RoutingKey 与 BindingKey 一般都使用 . 分隔单词,例如:

  • order.error
  • order.pay.info
  • pay.error

7.3 路由机制图

#mermaid-svg-06FzQBkdS8vsYyp4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-06FzQBkdS8vsYyp4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-06FzQBkdS8vsYyp4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-06FzQBkdS8vsYyp4 .error-icon{fill:#552222;}#mermaid-svg-06FzQBkdS8vsYyp4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-06FzQBkdS8vsYyp4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-06FzQBkdS8vsYyp4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-06FzQBkdS8vsYyp4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-06FzQBkdS8vsYyp4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-06FzQBkdS8vsYyp4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-06FzQBkdS8vsYyp4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-06FzQBkdS8vsYyp4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-06FzQBkdS8vsYyp4 .marker.cross{stroke:#333333;}#mermaid-svg-06FzQBkdS8vsYyp4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-06FzQBkdS8vsYyp4 p{margin:0;}#mermaid-svg-06FzQBkdS8vsYyp4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-06FzQBkdS8vsYyp4 .cluster-label text{fill:#333;}#mermaid-svg-06FzQBkdS8vsYyp4 .cluster-label span{color:#333;}#mermaid-svg-06FzQBkdS8vsYyp4 .cluster-label span p{background-color:transparent;}#mermaid-svg-06FzQBkdS8vsYyp4 .label text,#mermaid-svg-06FzQBkdS8vsYyp4 span{fill:#333;color:#333;}#mermaid-svg-06FzQBkdS8vsYyp4 .node rect,#mermaid-svg-06FzQBkdS8vsYyp4 .node circle,#mermaid-svg-06FzQBkdS8vsYyp4 .node ellipse,#mermaid-svg-06FzQBkdS8vsYyp4 .node polygon,#mermaid-svg-06FzQBkdS8vsYyp4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-06FzQBkdS8vsYyp4 .rough-node .label text,#mermaid-svg-06FzQBkdS8vsYyp4 .node .label text,#mermaid-svg-06FzQBkdS8vsYyp4 .image-shape .label,#mermaid-svg-06FzQBkdS8vsYyp4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-06FzQBkdS8vsYyp4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-06FzQBkdS8vsYyp4 .rough-node .label,#mermaid-svg-06FzQBkdS8vsYyp4 .node .label,#mermaid-svg-06FzQBkdS8vsYyp4 .image-shape .label,#mermaid-svg-06FzQBkdS8vsYyp4 .icon-shape .label{text-align:center;}#mermaid-svg-06FzQBkdS8vsYyp4 .node.clickable{cursor:pointer;}#mermaid-svg-06FzQBkdS8vsYyp4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-06FzQBkdS8vsYyp4 .arrowheadPath{fill:#333333;}#mermaid-svg-06FzQBkdS8vsYyp4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-06FzQBkdS8vsYyp4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-06FzQBkdS8vsYyp4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-06FzQBkdS8vsYyp4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-06FzQBkdS8vsYyp4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-06FzQBkdS8vsYyp4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-06FzQBkdS8vsYyp4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-06FzQBkdS8vsYyp4 .cluster text{fill:#333;}#mermaid-svg-06FzQBkdS8vsYyp4 .cluster span{color:#333;}#mermaid-svg-06FzQBkdS8vsYyp4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-06FzQBkdS8vsYyp4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-06FzQBkdS8vsYyp4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-06FzQBkdS8vsYyp4 .icon-shape,#mermaid-svg-06FzQBkdS8vsYyp4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-06FzQBkdS8vsYyp4 .icon-shape p,#mermaid-svg-06FzQBkdS8vsYyp4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-06FzQBkdS8vsYyp4 .icon-shape .label rect,#mermaid-svg-06FzQBkdS8vsYyp4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-06FzQBkdS8vsYyp4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-06FzQBkdS8vsYyp4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-06FzQBkdS8vsYyp4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} order.error
order.pay.info
pay.error
*.error
#.info
*.error
Producer
Topic Exchange
Queue 1
Queue 2
Consumer 1
Consumer 2

7.4 怎么用

java 复制代码
channel.exchangeDeclare("test_topic", BuiltinExchangeType.TOPIC, true, false, false, null);

channel.queueDeclare("topic_queue1", true, false, false, null);
channel.queueDeclare("topic_queue2", true, false, false, null);

channel.queueBind("topic_queue1", "test_topic", "*.error");
channel.queueBind("topic_queue2", "test_topic", "#.info");
channel.queueBind("topic_queue2", "test_topic", "*.error");

channel.basicPublish("test_topic", "order.error", null, "hello topic, I'm order.error".getBytes());
channel.basicPublish("test_topic", "order.pay.info", null, "hello topic, I'm order.pay.info".getBytes());
channel.basicPublish("test_topic", "pay.error", null, "hello topic, I'm pay.error".getBytes());

7.5 Topic 与 Direct 的区别

对比项 Direct Topic
匹配方式 精确匹配 通配符匹配
灵活性 较低 较高
适用场景 路由规则固定 路由规则复杂、多级分类

重点结论 :如果消息的 RoutingKey 没有匹配到任何 BindingKey,消息会被丢弃;如果设置了 mandatory,则有机会返回给生产者处理。

8. RPC:基于 RabbitMQ 的请求响应

8.1 是什么

RPC 模式 并不是传统意义上的单向消息投递,而是借助 RabbitMQ 实现"请求 - 响应"式通信。

它通常会用到两个关键属性:

  1. replyTo:指定回调队列。
  2. correlationId:标记请求与响应的对应关系。

8.2 工作流程

  1. 客户端把请求消息发到请求队列。
  2. 客户端在消息属性中设置 replyTo,告诉服务端把响应发到哪个回调队列。
  3. 服务端消费请求后,处理业务逻辑。
  4. 服务端把结果发送到 replyTo 指定的队列。
  5. 客户端监听回调队列,并根据 correlationId 匹配响应。

#mermaid-svg-ju0QIrll1FKYAIBk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ju0QIrll1FKYAIBk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ju0QIrll1FKYAIBk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ju0QIrll1FKYAIBk .error-icon{fill:#552222;}#mermaid-svg-ju0QIrll1FKYAIBk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ju0QIrll1FKYAIBk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ju0QIrll1FKYAIBk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ju0QIrll1FKYAIBk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ju0QIrll1FKYAIBk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ju0QIrll1FKYAIBk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ju0QIrll1FKYAIBk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ju0QIrll1FKYAIBk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ju0QIrll1FKYAIBk .marker.cross{stroke:#333333;}#mermaid-svg-ju0QIrll1FKYAIBk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ju0QIrll1FKYAIBk p{margin:0;}#mermaid-svg-ju0QIrll1FKYAIBk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ju0QIrll1FKYAIBk .cluster-label text{fill:#333;}#mermaid-svg-ju0QIrll1FKYAIBk .cluster-label span{color:#333;}#mermaid-svg-ju0QIrll1FKYAIBk .cluster-label span p{background-color:transparent;}#mermaid-svg-ju0QIrll1FKYAIBk .label text,#mermaid-svg-ju0QIrll1FKYAIBk span{fill:#333;color:#333;}#mermaid-svg-ju0QIrll1FKYAIBk .node rect,#mermaid-svg-ju0QIrll1FKYAIBk .node circle,#mermaid-svg-ju0QIrll1FKYAIBk .node ellipse,#mermaid-svg-ju0QIrll1FKYAIBk .node polygon,#mermaid-svg-ju0QIrll1FKYAIBk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ju0QIrll1FKYAIBk .rough-node .label text,#mermaid-svg-ju0QIrll1FKYAIBk .node .label text,#mermaid-svg-ju0QIrll1FKYAIBk .image-shape .label,#mermaid-svg-ju0QIrll1FKYAIBk .icon-shape .label{text-anchor:middle;}#mermaid-svg-ju0QIrll1FKYAIBk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ju0QIrll1FKYAIBk .rough-node .label,#mermaid-svg-ju0QIrll1FKYAIBk .node .label,#mermaid-svg-ju0QIrll1FKYAIBk .image-shape .label,#mermaid-svg-ju0QIrll1FKYAIBk .icon-shape .label{text-align:center;}#mermaid-svg-ju0QIrll1FKYAIBk .node.clickable{cursor:pointer;}#mermaid-svg-ju0QIrll1FKYAIBk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ju0QIrll1FKYAIBk .arrowheadPath{fill:#333333;}#mermaid-svg-ju0QIrll1FKYAIBk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ju0QIrll1FKYAIBk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ju0QIrll1FKYAIBk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ju0QIrll1FKYAIBk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ju0QIrll1FKYAIBk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ju0QIrll1FKYAIBk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ju0QIrll1FKYAIBk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ju0QIrll1FKYAIBk .cluster text{fill:#333;}#mermaid-svg-ju0QIrll1FKYAIBk .cluster span{color:#333;}#mermaid-svg-ju0QIrll1FKYAIBk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ju0QIrll1FKYAIBk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ju0QIrll1FKYAIBk rect.text{fill:none;stroke-width:0;}#mermaid-svg-ju0QIrll1FKYAIBk .icon-shape,#mermaid-svg-ju0QIrll1FKYAIBk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ju0QIrll1FKYAIBk .icon-shape p,#mermaid-svg-ju0QIrll1FKYAIBk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ju0QIrll1FKYAIBk .icon-shape .label rect,#mermaid-svg-ju0QIrll1FKYAIBk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ju0QIrll1FKYAIBk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ju0QIrll1FKYAIBk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ju0QIrll1FKYAIBk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} request + replyTo + correlationId
response + correlationId
Client
RPC Request Queue
RPC Server
Callback Queue

在实际开发中,一个客户端通常会同时发送很多个异步请求,但它们都复用同一个回调队列. 客户端通过监听回调队列中的correlationId就可以通过查询本地的内存HashMap, 找出和correlationId对应的业务数据,然后把业务数据交给那个等在原地的业务线程

8.3 为什么 RPC 里常配合 QoS 和手动 ACK

RPC 场景下通常希望"一个请求对应一个可靠响应",因此服务端使用了:

  1. channel.basicQos(1):限制同一时间只取一个未确认消息。
  2. autoAck = false:关闭自动确认。
  3. basicAck(...):业务处理完后再确认消息。
java 复制代码
channel.basicQos(1);

Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
                               AMQP.BasicProperties properties, byte[] body) throws IOException {
        AMQP.BasicProperties replyProps = new AMQP.BasicProperties.Builder()
                .correlationId(properties.getCorrelationId())
                .build();

        String message = new String(body);
        String response = "request:" + message + ", response: 处理成功";

        channel.basicPublish("", properties.getReplyTo(), replyProps, response.getBytes());
        channel.basicAck(envelope.getDeliveryTag(), false);
    }
};

channel.basicConsume("rpc_request_queue", false, consumer);

关键理解:RPC 虽然也基于消息队列,但它的目标不是解耦异步,而是实现带返回值的消息通信。

9. 消息可靠性:确认机制、QoS 与持久化

9.1 消费者确认机制:autoAck 与 manualAck

9.1.1 自动确认

autoAck=true 时,RabbitMQ 在消息投递给消费者后,就会立即认为消息已经消费成功。

优点:

  1. 编码简单。
  2. 吞吐量高。

缺点:

  1. 如果消费者处理失败,消息可能已经丢失。

9.1.2 手动确认

autoAck=false 时,消费者处理完业务后必须显式调用 basicAck

优点:

  1. 更安全。
  2. 更适合重要消息。

缺点:

  1. 编程稍复杂。
  2. 如果忘记 ACK,消息会长期处于未确认状态。
方式 优点 缺点 适用场景
autoAck=true 简单、吞吐高 容易丢消息 日志、低价值消息
autoAck=false 更可靠 代码复杂度更高 订单、支付、重要任务

建议 :凡是业务上不能丢的消息,优先考虑 手动 ACK

9.2 QoS / prefetchCount

课程中提到,如果不设置 basicQos,默认 prefetchCount=0,Broker 可能一次给消费者推送多条消息。

这会导致:

  1. 某个消费者拿到过多消息。
  2. 消费不均衡。
  3. 处理慢的消费者积压未确认消息。

因此可以使用:

java 复制代码
channel.basicQos(1);

它表示:在当前消费者未确认前,不再继续分发新的消息给它。

这就是公平分发的核心手段之一,尤其适合 Work Queues 和 RPC 场景。

9.3 持久化

这里强调了 队列和交换机的 durable 参数

例如:

  • queueDeclare(..., true, ...):队列持久化。
  • exchangeDeclare(..., true, ...):交换机持久化。

其意义在于:RabbitMQ 重启后,相关元数据不会丢失。

仅仅队列持久化还不够,真正要做到高可靠,通常还需要配合 消息持久化发布确认 一起使用。

10. Publisher Confirms:发布确认机制

10.1 是什么

Publisher Confirms 用于解决"生产者把消息发出去后,到底 RabbitMQ 收没收到"的问题。

它是 发送端可靠性 的核心机制。

10.2 核心原理

  1. 生产者调用 channel.confirmSelect() 开启确认模式。
  2. 之后发送的每条消息都会分配一个递增序号。
  3. Broker 成功接收后,异步返回 ACK
  4. 如果 Broker 因内部错误导致失败,则可能返回 NACK

10.3 三种确认策略

策略 做法 优点 缺点
单条同步确认 每发一条就 waitForConfirmsOrDie 最直观,最安全 性能最差
批量确认 每发一批后统一确认 性能更高 出错后不易定位具体哪条失败
异步确认 addConfirmListener 注册回调 性能最好 编程复杂度最高

10.4 单条确认示例

java 复制代码
channel.confirmSelect();

channel.basicPublish("", "publisher_confirms_queue1", null, "msg".getBytes());
channel.waitForConfirmsOrDie(5000);

10.5 异步确认思路

  1. 使用 addConfirmListener 监听 ACK / NACK。
  2. 用一个有序集合保存未确认消息序号。
  3. 收到 ACK 后删除对应序号。
  4. 收到 NACK 后进行重发、记录日志或补偿处理。
java 复制代码
channel.confirmSelect();
SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<>());

channel.addConfirmListener(new ConfirmListener() {
    @Override
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
        if (multiple) {
            confirmSet.headSet(deliveryTag + 1).clear();
        } else {
            confirmSet.remove(deliveryTag);
        }
    }

    @Override
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
        if (multiple) {
            confirmSet.headSet(deliveryTag + 1).clear();
        } else {
            confirmSet.remove(deliveryTag);
        }
    }
});

核心结论:发布确认解决的是"消息有没有到 Broker",而消费者 ACK 解决的是"消息有没有被业务成功处理"。两者不是一回事。

11. Spring Boot 整合 RabbitMQ

11.1 为什么 Spring Boot 开发更高效

相比直接使用 amqp-client,Spring Boot 整合后有几个明显优势:

  1. 自动装配连接工厂与模板对象。
  2. 可以用 RabbitTemplate 统一发送消息。
  3. 可以用 @RabbitListener 直接监听队列。
  4. 可以通过 QueueBuilder / ExchangeBuilder / BindingBuilder 声明基础设施。

11.2 工作队列模式整合思路

声明队列
java 复制代码
@Configuration
public class RabbitMQConfig {

    @Bean("workQueue")
    public Queue workQueue() {
        return QueueBuilder.durable("work_queue").build();
    }
}

使用durable可以创建一个持久化的工作队列,这样一来即使 RabbitMQ 服务器重启、崩溃或断电,队列和其中的消息仍然会被保留。

反之,noDurable可以创建一个非持久化的队列

发送消息
java 复制代码
@RestController
@RequestMapping("/producer")
public class ProducerController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/work")
    public String work() {
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("", "work_queue", "hello spring amqp: work...");
        }
        return "发送成功";
    }
}

此时我们直接在浏览器中访问127.0.0.1:8080/producer/work对应的url,

然后输入http://47.116.96.217:15672访问 RabbitMQ控制台,可以看到相应的工作队列已经被创建,且已经有了十条消息

先前的队列是我之前做项目创建的,这里可以先忽略掉

监听消息

使用@RabbitListener 可以实现消息的消费, 在注解内可以配置绑定的队列名,队列名这类常量我们也可以写一个constants包来统一管理

java 复制代码
@Component  
public class WorkListener {  
  
    @RabbitListener(queues = Constants.WORK_QUEUE)  
    public void work(Message message, Channel channel){  
        System.out.println("listener 1 ["+Constants.WORK_QUEUE+"] 接收到消息:" +message + ",channel:"+channel);  
    }  
  
    @RabbitListener(queues = Constants.WORK_QUEUE)  
    public void queueListener2(String message){  
        System.out.println("listener 2 ["+Constants.WORK_QUEUE+"] 接收到消息:" +message);  
    }  
}

此时再次重启项目,调用producer/work接口来发送消息,观察控制台的日志结果

以上展示了 RabbitMQ 最经典的工作队列模式:

  1. 10 条消息被两个消费者均分
    2.每条消息只被消费一次
  2. 自动负载均衡(轮询)

11.3 Fanout / Direct / Topic 在 Spring Boot 中的声明方式

Fanout (广播模式)

配置广播模式交换机和队列,并建立绑定关系:

java 复制代码
@Bean("fanoutQueue1")  
public Queue fanoutQueue1() {  
    return QueueBuilder.durable(Constants.FANOUT_QUEUE1).build();  
}  
@Bean("fanoutQueue2")  
public Queue fanoutQueue2() {  
    return QueueBuilder.durable(Constants.FANOUT_QUEUE2).build();  
}  
//声明交换机  
@Bean("fanoutExchange")  
public FanoutExchange fanoutExchange() {  
    return ExchangeBuilder.fanoutExchange(Constants.FANOUT_EXCHANGE).durable(true).build();  
}  
//队列和交换机绑定  
@Bean  
public Binding fanoutBinding(@Qualifier("fanoutExchange") FanoutExchange exchange, @Qualifier("fanoutQueue1") Queue queue) {  
    return BindingBuilder.bind(queue).to(exchange);  
}  
@Bean  
public Binding fanoutBinding2(@Qualifier("fanoutExchange") FanoutExchange exchange, @Qualifier("fanoutQueue2") Queue queue) {  
    return BindingBuilder.bind(queue).to(exchange);  
}

设置消息生产者:

java 复制代码
@RequestMapping("/fanout")  
public String fanoutProduct(@RequestParam(defaultValue = "10") int count){  
    for (int i = 0; i < count; i++) {  
        rabbitTemplate.convertAndSend(Constants.FANOUT_EXCHANGE, "", "hello spring amqp: fanout..." + i);  
    }  
    return "ok";  
}

配置FanoutListener

java 复制代码
@Component  
public class FanoutListener {  
    //指定监听队列的名称  
    @RabbitListener(queues = Constants.FANOUT_QUEUE1)  
    public void ListenerQueue(String message){  
        System.out.println("["+Constants.FANOUT_QUEUE1+ "]接收到消息:"+ message);  
    }  
    @RabbitListener(queues = Constants.FANOUT_QUEUE2)  
    public void ListenerQueue2(String message){  
        System.out.println("["+Constants.FANOUT_QUEUE2+ "]接收到消息:"+ message);  
    }  
}

访问对应接口,传入count参数

Direct 示例(路由模式)

配置路由模式交换机,队列,绑定关系

java 复制代码
//路由模式

    @Bean("directQueue1")

    public Queue directQueue1(){

        return QueueBuilder.durable(Constants.DIRECT_QUEUE1).build();

    }

    @Bean("directQueue2")

    public Queue directQueue2(){

        return QueueBuilder.durable(Constants.DIRECT_QUEUE2).build();

    }

    @Bean("directExchange")

    public DirectExchange directExchange(){

        return ExchangeBuilder.directExchange(Constants.DIRECT_EXCHANGE).durable(true).build();

    }

    @Bean("directQueueBinding1")

    public Binding directQueueBinding1(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue1") Queue queue){

        return BindingBuilder.bind(queue).to(directExchange).with("orange");

    }

    @Bean("directQueueBinding2")

    public Binding directQueueBinding2(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue2") Queue queue){

        return BindingBuilder.bind(queue).to(directExchange).with("black");

    }

    @Bean("directQueueBinding3")

    public Binding directQueueBinding3(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("directQueue2") Queue queue){

        return BindingBuilder.bind(queue).to(directExchange).with("orange");

    }

接口设计

java 复制代码
@RequestMapping("/direct")  
public String directProduct(@RequestParam(defaultValue = "10") String routingKey){  
    rabbitTemplate.convertAndSend(Constants.DIRECT_EXCHANGE, routingKey, "hello spring amqp:direct, my routing key is "+routingKey);  
    return "发送成功";  
}
Topic (通配符模式)
java 复制代码
//通配符模式  
@Bean("topicQueue1")  
public Queue topicQueue1(){  
    return QueueBuilder.durable(Constants.TOPIC_QUEUE1).build();  
}  
@Bean("topicQueue2")  
public Queue topicQueue2(){  
    return QueueBuilder.durable(Constants.TOPIC_QUEUE2).build();  
}  
@Bean("topicExchange")  
public TopicExchange topicExchange(){  
    return ExchangeBuilder.topicExchange(Constants.TOPIC_EXCHANGE).durable(true).build();  
}  
  
@Bean("topicQueueBinding1")  
public Binding topicQueueBinding1(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue1") Queue queue){  
    return BindingBuilder.bind(queue).to(topicExchange).with("*.orange.*");  
}  
@Bean("topicQueueBinding2")  
public Binding topicQueueBinding2(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue2") Queue queue){  
    return BindingBuilder.bind(queue).to(topicExchange).with("*.*.rabbit");  
}  
@Bean("topicQueueBinding3")  
public Binding topicQueueBinding3(@Qualifier("topicExchange") TopicExchange topicExchange, @Qualifier("topicQueue2") Queue queue){  
    return BindingBuilder.bind(queue).to(topicExchange).with("lazy.#");  
}
java 复制代码
@RequestMapping("/topic/{routingKey}")  
public String topic(@PathVariable("routingKey") String routingKey){  
    rabbitTemplate.convertAndSend(Constants.TOPIC_EXCHANGE, routingKey, "hello spring amqp:topic, my routing key is "+routingKey);  
    return "发送成功";  
}
java 复制代码
@Component  
public class DirectListener {  
  
    @RabbitListener(queues = Constants.DIRECT_QUEUE1)  
    public void queueListener1(String message){  
        System.out.println("队列["+Constants.DIRECT_QUEUE1+"] 接收到消息:" +message);  
    }  
  
    @RabbitListener(queues = Constants.DIRECT_QUEUE2)  
    public void queueListener2(String message){  
        System.out.println("队列["+Constants.DIRECT_QUEUE2+"] 接收到消息:" +message);  
    }  
}

访问 http://127.0.0.1:8080/producer/topic/lazy.orange.cheems

此时通过路径传参,rountingKey为lazy.orange.cheems ,topic.queue1和topic.queue2都能接收到消息

11.4 @RabbitListener 的理解

@RabbitListener 可以接收多种类型参数,例如:

  1. String:直接拿到消息内容。
  2. Message:拿到原始消息体和属性。
  3. Channel:便于手动确认消息等高级操作。

如果只是快速开发,String 够用;如果要处理消息头、投递信息或手动 ACK,优先使用 MessageChannel

这里要使用rabbit提供的原生api接口,注意不到导包错误, 否则会报错~~

⚠️ 踩坑指南 / 最佳实践

  1. 不要把 Exchange 当成存储组件。 交换机只负责路由,不负责保存消息;没有匹配队列时,消息可能直接丢失。
  2. 生产者通常发给交换机,而不是直接发给业务队列。 直接发队列虽然简单,但扩展性差。
  3. Direct 和 Topic 最容易混淆。 Direct 是完全匹配,Topic 才支持 *# 通配符。
  4. Work Queues 场景下建议配合 basicQos(1) 否则可能出现某个消费者拿到过多消息,负载不均衡。
  5. 重要业务不要默认开启 autoAck=true 自动确认会在业务未真正处理完成时就删除消息,存在丢消息风险。
  6. 开启持久化不等于绝对可靠。 想要完整保障链路,通常要同时考虑队列持久化、消息持久化、消费者 ACK、发布确认。
  7. 未匹配路由的消息要重点关注。 课程中特别提到可以通过 mandatory 让不可路由消息返回给生产者,而不是悄悄丢失。
  8. RPC 场景必须注意 replyTocorrelationId 少了这两个字段,请求与响应很难正确对应。
  9. Spring Boot 整合时,建议把队列、交换机、绑定关系统一放到配置类中管理。 这样结构更清晰,也更方便维护。

✍️ 复习与自测

  1. RoutingKeyBindingKey 有什么区别?它们分别在哪个阶段发挥作用?
  2. 为什么说 Fanout、Direct、Topic 三种交换机分别适合不同复杂度的业务路由?
  3. Work Queues 模式下,如果不设置 basicQos(1),可能出现什么问题?
  4. Publisher Confirms 和消费者 ACK 分别解决的是哪一段链路的可靠性问题?
  5. Spring Boot 中,为什么推荐使用 QueueBuilder / ExchangeBuilder / BindingBuilder 配置 RabbitMQ 基础设施?
相关推荐
齐 飞1 小时前
JDK21虚拟线程
java·后端
小马爱打代码2 小时前
Java 并发 Bug 深度分析与实战
java
极客先躯2 小时前
高级java每日一道面试题-2026年02月09日-实战篇[Docker]-Docker 容器有哪些安全风险?如何缓解?
java·运维·网络·安全·docker·容器
_Aaron___2 小时前
MyBatis 动态排序别乱用 ${}:ORDER BY 的安全写法
java·spring·mybatis
摇滚侠2 小时前
SpringMVC 入门到实战 HttpMessageConverter 65-74
java·后端·spring·intellij-idea
逢君学术论文AI写作2 小时前
Java第24课:会话技术CookieSession
java·开发语言
小小编程路2 小时前
字符串转数字时,可能会遇到哪些问题?
java·开发语言·算法
许彰午2 小时前
责任链模式实战——同一个框架里的两种链
java·开发语言·责任链模式
寻道码路2 小时前
LangChain4j Java AI 应用开发实战(十四):手写 RAG 全流程 - 深入理解每个环节
java·开发语言·人工智能·ai
云烟成雨TD2 小时前
Agent Scope Java 2.x 系列【1】核心架构
java·人工智能·agent