RabbitMQ 从零到实战:概念、配置与 Spring Boot 集成指南

RabbitMQ 从零到实战:概念、配置与 Spring Boot 集成指南

一、RabbitMQ 核心概念

1.1 AMQP 模型中的角色

复制代码
Producer(生产者)
    │
    │ 发送消息
    ▼
Exchange(交换机) ──── Binding(绑定规则) ──── Queue(队列)
                                                │
                                                │ 消费消息
                                                ▼
                                         Consumer(消费者)
角色 说明
Producer 消息发送方,将消息发送到 Exchange
Exchange 接收消息并按规则路由到 Queue。消息不直接发到 Queue
Binding Exchange 和 Queue 之间的绑定关系,定义路由规则
Queue 消息最终存储的地方,消费者从这里拉取消息
Consumer 消息接收方,从 Queue 中消费消息
Routing Key 消息携带的路由键,Exchange 根据它决定发到哪个 Queue
Virtual Host 逻辑隔离单元,类似数据库的 schema,不同 vhost 之间资源互不可见

1.2 Exchange 四种类型

类型 路由规则 典型场景
Direct Routing Key 精确匹配 点对点,如订单处理
Fanout 广播到所有绑定的 Queue,忽略 Routing Key 通知广播,如日志
Topic Routing Key 模式匹配(* 匹配一个词,# 匹配多个词) 灵活路由,如 order.createorder.*
Headers 根据消息头属性匹配(几乎不用) 特殊场景

1.3 与 Kafka 等术语的对应关系

很多人混淆 RabbitMQ 和 Kafka 的术语:

概念 RabbitMQ Kafka RocketMQ
消息存储单元 Queue Partition (within Topic) Queue (within Topic)
消息分类/频道 Exchange + Routing Key Topic Topic
消费者分组 多个 Consumer 消费同一 Queue(竞争消费) Consumer Group Consumer Group
消息广播 Fanout Exchange 绑定多个 Queue 不同 Consumer Group 不同 Consumer Group

注意:RabbitMQ 中没有原生的 "Topic"(作为消息分类的概念),它用 Exchange + Queue + Binding 的组合来实现类似能力。Kafka 和 RocketMQ 才有 Topic 的概念。

1.4 消费模式

竞争消费(Work Queue)

  • 多个 Consumer 监听同一个 Queue
  • 一条消息只被其中一个 Consumer 消费
  • 用于负载均衡

发布订阅(Pub/Sub)

  • 使用 Fanout Exchange 绑定多个 Queue
  • 每个 Queue 对应一个 Consumer
  • 一条消息被所有 Consumer 消费
  • 用于广播通知

1.5 消息确认机制(ACK)

模式 说明 风险
自动确认(auto-ack) 消息发送给 Consumer 后立即从 Queue 删除 Consumer 处理失败则消息丢失
手动确认(manual-ack) Consumer 处理完后主动发 ACK,Queue 才删除消息 更安全,但需处理超时和重复投递
拒绝(reject/nack) Consumer 明确拒绝消息,可选择重新入队或丢弃 死循环风险(不断重新入队)

1.6 消息持久化

  • Queue 持久化 :Broker 重启后 Queue 定义不丢失(durable=true
  • 消息持久化 :消息写入磁盘,Broker 重启后消息不丢失(deliveryMode=2
  • Exchange 持久化:Broker 重启后 Exchange 不丢失

三者都要配置才能保证消息在 Broker 故障恢复后不丢。


注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

二、首次申请和部署 RabbitMQ

2.1 方式一:本地安装(开发环境)

Docker 方式(推荐)

bash 复制代码
docker run -d --name rabbitmq \
  -p 5672:5672 \
  -p 15672:15672 \
  -e RABBITMQ_DEFAULT_USER=admin \
  -e RABBITMQ_DEFAULT_PASS=admin123 \
  rabbitmq:3-management
  • 5672:AMQP 协议端口(程序连接用)
  • 15672:管理控制台端口(浏览器访问)
  • 访问 http://localhost:15672 使用 admin/admin123 登录管理后台

2.2 方式二:云服务(生产环境)

以阿里云 AMQP(即本文参考工程使用的方式)为例:

申请步骤

  1. 登录阿里云控制台 → 搜索"消息队列 AMQP"
  2. 创建实例(选择地域、规格)
  3. 获取实例信息:
    • 接入点地址(addresses):如 amqp-cn-xxx.mq-amqp.cn-qingdao-xxx.aliyuncs.com
    • 实例 ID(instance-id)
    • AccessKey ID / Secret
    • 用户名/密码(由 AccessKey 生成的 Base64 编码)
  4. 在实例控制台中创建 Virtual Host
  5. 创建 Exchange
  6. 创建 Queue
  7. 建立 Binding(将 Queue 绑定到 Exchange,设置 Routing Key)

2.3 管理后台中的操作

在 RabbitMQ Management UI 中需要手动或通过 API 创建:

复制代码
1. 创建 Exchange
   - Name: my-app.exchange
   - Type: direct
   - Durable: true

2. 创建 Queue
   - Name: my-app.import-task
   - Durable: true

3. 创建 Binding
   - Source: my-app.exchange
   - Destination: my-app.import-task
   - Routing Key: import-task(通常和 Queue 名相同)

三、Spring Boot 集成 RabbitMQ 配置详解

3.1 配置

yaml 复制代码
spring:
  rabbitmq:
    # AMQP 接入点地址(阿里云格式,自建则为 host:port)
    addresses: amqp-cn-xxx5.mq-amqp.cn-qingdao-xxxx-a.aliyuncs.com

    # 认证信息(阿里云 AMQP 用 Base64 编码的 AccessKey)
    username: xxxx...(Base64编码)
    password: xxxxxx...(Base64编码)

    # 阿里云 AMQP 特有配置
    access-key-id: xxxxxx
    access-key-secret: xxxx
    instance-id: amqp-cn-xxxx
    region: cn-qingdao

    # 消费者监听配置
    listener:
      simple:
        # 消费失败时不重新入队(避免死循环)
        default-requeue-rejected: false
        # 最小并发消费者数
        concurrency: 1
        # 最大并发消费者数
        max-concurrency: 3
        # 预取数量(每次从 Broker 拉取几条未确认的消息)
        prefetch: 1

3.2 配置项含义详解

配置项 含义 建议值
addresses Broker 地址,多个用逗号分隔 生产用集群地址
username / password 连接认证 不要用默认 guest
virtual-host 虚拟主机,逻辑隔离 按环境或团队划分
connection-timeout 连接超时 5000~10000ms
listener.simple.concurrency 每个 @RabbitListener 启动的最小消费者线程数 IO 密集设 2~5
listener.simple.max-concurrency 最大消费者线程数 5~20
listener.simple.prefetch 每个消费者一次预取的消息数 1(保证公平分发),高吞吐设 10~50
listener.simple.default-requeue-rejected 消费异常时是否重新入队 false(配合死信队列)

3.3 自建 RabbitMQ 的标准配置

yaml 复制代码
spring:
  rabbitmq:
    host: 192.168.1.1xx
    port: 5672
    username: myapp
    password: myapp123
    virtual-host: /myapp
    connection-timeout: 5000
    listener:
      simple:
        acknowledge-mode: manual      # 手动确认
        concurrency: 2
        max-concurrency: 5
        prefetch: 1
        default-requeue-rejected: false
    publisher-confirm-type: correlated  # 生产者确认
    publisher-returns: true             # 消息无法路由时回调

四、完整示例:Spring Boot + RabbitMQ 异步导入

4.1 依赖(pom.xml)

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

4.2 配置(application.yml)

yaml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin123
    virtual-host: /

app:
  mq:
    exchange: app.exchange
    queue: app.import-task
    routing-key: import-task

4.3 RabbitMQ 资源声明(自动创建 Exchange、Queue、Binding)

java 复制代码
package com.example.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMQ 资源配置.
 * 应用启动时自动创建 Exchange、Queue 并建立绑定关系.
 */
@Configuration
public class RabbitMqConfig {

  @Value("${app.mq.exchange}")
  private String exchangeName;

  @Value("${app.mq.queue}")
  private String queueName;

  @Value("${app.mq.routing-key}")
  private String routingKey;

  /** 声明持久化交换机. */
  @Bean
  public DirectExchange importExchange() {
    return new DirectExchange(exchangeName, true, false);
  }

  /** 声明持久化队列. */
  @Bean
  public Queue importQueue() {
    return new Queue(queueName, true);
  }

  /** 建立绑定关系. */
  @Bean
  public Binding importBinding(Queue importQueue, DirectExchange importExchange) {
    return BindingBuilder.bind(importQueue).to(importExchange).with(routingKey);
  }
}

4.4 消息体 DTO

java 复制代码
package com.example.dto;

import java.io.Serializable;
import java.util.List;
import lombok.Data;

/**
 * 导入任务消息体.
 */
@Data
public class ImportTaskMessage implements Serializable {

  /** 批次ID. */
  private Long batchId;

  /** 批次号. */
  private String batchNo;

  /** 待插入的数据(JSON序列化后体积较大时,可改为只传batchId,消费端自己查数据). */
  private List<ImportRecord> records;
}

4.5 生产者(发送消息)

java 复制代码
package com.example.mq;

import com.example.dto.ImportTaskMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;

/**
 * 导入任务消息生产者.
 */
@Slf4j
@Component
public class ImportTaskProducer {

  @Resource
  private RabbitTemplate rabbitTemplate;

  @Value("${app.mq.exchange}")
  private String exchange;

  @Value("${app.mq.routing-key}")
  private String routingKey;

  /**
   * 发送导入任务消息.
   */
  public void send(ImportTaskMessage message) {
    log.info("发送导入任务消息,批次号:{}", message.getBatchNo());
    rabbitTemplate.convertAndSend(exchange, routingKey, message);
    log.info("导入任务消息发送完成");
  }
}

4.6 消费者(接收并处理消息)

java 复制代码
package com.example.mq;

import com.example.dto.ImportRecord;
import com.example.dto.ImportTaskMessage;
import com.example.mapper.ImportRecordMapper;
import jakarta.annotation.Resource;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * 导入任务消息消费者.
 */
@Slf4j
@Component
public class ImportTaskConsumer {

  private static final int BATCH_SIZE = 2000;

  @Resource
  private ImportRecordMapper importRecordMapper;

  /**
   * 消费导入任务.
   * 使用 @RabbitListener 注解声明监听哪个队列.
   */
  @RabbitListener(queues = "${app.mq.queue}")
  public void consume(ImportTaskMessage message) {
    log.info("收到导入任务,批次号:{},数据量:{}", 
        message.getBatchNo(), message.getRecords().size());

    try {
      List<ImportRecord> records = message.getRecords();
      int total = records.size();

      // 分批插入
      for (int i = 0; i < total; i += BATCH_SIZE) {
        int end = Math.min(i + BATCH_SIZE, total);
        List<ImportRecord> batch = records.subList(i, end);
        importRecordMapper.batchInsert(batch);
      }

      log.info("导入任务处理完成,批次号:{},共插入{}条", message.getBatchNo(), total);
    } catch (Exception e) {
      log.error("导入任务处理失败,批次号:{}", message.getBatchNo(), e);
      // 抛出异常后,根据 default-requeue-rejected 配置决定是否重新入队
      throw e;
    }
  }
}

4.7 Service 层(校验通过后发消息)

java 复制代码
package com.example.service.impl;

import com.example.dto.ImportRecord;
import com.example.dto.ImportTaskMessage;
import com.example.mq.ImportTaskProducer;
import com.example.service.ImportService;
import jakarta.annotation.Resource;
import java.util.List;
import org.springframework.stereotype.Service;

@Service
public class ImportServiceImpl implements ImportService {

  @Resource
  private ImportTaskProducer producer;

  @Override
  public String importData(List<ImportRecord> records, String operatorId) {
    // 同步校验
    for (int i = 0; i < records.size(); i++) {
      if (records.get(i).getAmount() <= 0) {
        throw new RuntimeException("第" + (i + 1) + "行:数量必须大于0");
      }
    }

    // 保存主表...(略)
    String batchNo = "20260610001";

    // 发送MQ消息,异步执行插入
    ImportTaskMessage message = new ImportTaskMessage();
    message.setBatchId(1L);
    message.setBatchNo(batchNo);
    message.setRecords(records);
    producer.send(message);

    return "导入任务已提交,批次号:" + batchNo;
  }
}

4.8 执行时序

复制代码
请求线程                      RabbitMQ Broker              消费者线程
   │                              │                          │
   │── 同步校验 ──→                │                          │
   │── 保存主表 ──→                │                          │
   │── producer.send(msg) ──→     │                          │
   │                              │── 持久化消息 ──→           │
   │← 返回"任务已提交" ──          │                          │
   │                              │── 推送消息 ──→            │
   │  (HTTP已响应)                 │                          │── 批量INSERT
   │                              │                          │── ...
   │                              │                          │── INSERT完成
   │                              │←── ACK ──                │
   │                              │── 删除消息               │

五、生产环境最佳实践

5.1 消息可靠投递(不丢消息)

复制代码
Producer ──确认──→ Exchange ──确认──→ Queue ──确认──→ Consumer
   │                  │                │               │
   │ publisher-confirm │ mandatory     │ 持久化        │ 手动ACK
  • Publisher Confirm:Broker 收到消息后回调确认
  • Mandatory:消息无法路由到任何 Queue 时回调
  • Queue 持久化 + 消息持久化:Broker 重启不丢
  • 手动 ACK:Consumer 处理完才确认

6.2 消费幂等性

消息可能被重复投递(网络抖动、Consumer 超时未ACK),消费逻辑必须幂等:

  • 用唯一业务ID做去重判断
  • 使用数据库唯一约束
  • 先查后插的 "SELECT + INSERT" 模式

6.3 死信队列(DLQ)

消费失败的消息不要无限重试,设置死信队列:

复制代码
正常 Queue ──(消费失败/超时/被拒绝)──→ 死信 Exchange ──→ 死信 Queue

运维人员可以查看死信队列中的消息,分析失败原因后手动处理或重新投递。

6.4 消息体大小

  • RabbitMQ 默认单条消息上限 128MB,但建议控制在 1MB 以内
  • 大量数据(如12万条记录)不要直接放消息体里
  • 推荐做法:消息体只放 batchId,消费者根据 ID 去数据库/文件系统取数据

七、总结

阶段 关键动作
申请资源 创建 RabbitMQ 实例(云服务或自建)→ 获取连接信息
资源规划 设计 Exchange、Queue、Binding 的命名和路由关系
配置接入 application.yml 中配置连接信息和消费者参数
代码开发 声明资源 → 生产者发送 → 消费者处理
可靠性保障 Publisher Confirm + 消息持久化 + 手动ACK + 死信队列
运维监控 Management UI 监控队列积压、消费速率、死信数量
相关推荐
夜郎king1 小时前
SpringBoot 整合 Neo4j 实战:从零搭建经典小说知识图谱完整方案
spring boot·知识图谱·neo4j
仙俊红2 小时前
深入理解 ThreadLocal —— 从变量引用、强弱引用到 Spring Boot 实战
spring boot·python·算法
Jabes.yang2 小时前
互联网大厂Java求职面试实战解析(含技术场景与详解)
spring boot·微服务·面试·orm·技术栈·java se·jakarta ee
xujinwei_gingko10 小时前
SpringBoot整合WebSocket
spring boot·后端·websocket
来杯@Java11 小时前
学生选课管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·maven·mybatis
invicinble12 小时前
easyexcel开发全域理解
spring boot
逍遥德16 小时前
MQTT教程详解-05.SpringBoot集成mqtt client 性能分析
java·spring boot·spring·mt
点燃大海16 小时前
SpringAI构建智能体
java·spring boot·spring·springai智能体
xier_ran17 小时前
【infra之路】02_RadixAttention与KV_Cache管理
java·spring boot·spring