SpringBoot 整合 RabbitMQ
一、RabbitMQ 核心概念
RabbitMQ 是基于 AMQP(高级消息队列协议)的开源消息中间件,用于实现系统间的异步通信、解耦、削峰填谷。核心角色包括:
- 生产者(Producer):发送消息的应用。
- 消费者(Consumer):接收并处理消息的应用。
- 交换机(Exchange):接收生产者消息,根据路由规则转发到队列。
- 队列(Queue):存储消息,供消费者获取。
- 绑定(Binding):关联交换机和队列,定义路由规则。
核心交换机类型:
- Direct:精准匹配路由键(Routing Key)。
- Topic:模糊匹配路由键(支持 *单个词、#多个词匹配)。
- Fanout:广播消息到所有绑定的队列,忽略路由键。
二、使用 RabbitMQ 的优势
- 解耦:系统间通过消息通信,无需直接依赖,降低耦合度。
- 异步通信:生产者发送消息后无需等待响应,提升系统吞吐量。
- 削峰填谷:高并发场景下,消息队列缓冲请求,避免下游服务被压垮。
- 可靠性:支持消息持久化、确认机制、重试机制,确保消息不丢失。
- 灵活性:多种交换机类型适配不同业务场景(点对点、广播、主题路由)。
三、SpringBoot 整合 RabbitMQ 实战
环境准备
- JDK
- SpringBoot 2.7.x
- RabbitMQ 3.11+(安装教程:RabbitMQ 官方指南,启动后默认端口 5672,管理界面端口 15672,默认账号密码 guest/guest)
参考博客:
erlang下载地址:https://www.erlang.org/patches/otp-25.3
rabbitmq下载地址:https://www.rabbitmq.com/docs/install-windows
安装教程:https://blog.csdn.net/yangkeOK/article/details/135157985
可能遇到的问题1:https://cloud.tencent.com/developer/article/1710660
可能遇到的问题2:https://blog.csdn.net/qq_46092061/article/details/139049579
可能遇到的问题3:https://blog.csdn.net/qq_45059975/article/details/120960213
1. 项目初始化与依赖

(1)创建 SpringBoot 项目
通过 Maven创建项目,名为SpringBootRabbitMQ-Demo。
(2)核心依赖(pom.xml)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- SpringBoot父依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.4</version>
        <relativePath/>
    </parent>
    <groupId>com.yqd</groupId>
    <artifactId>RabbitMQ-Demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>RabbitMQ-Demo</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!-- SpringBoot Web:用于测试接口 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- SpringBoot RabbitMQ 整合依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!-- Lombok:简化代码 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
2. 核心配置(application.yml)
配置 RabbitMQ 连接信息、消息序列化方式:
spring:
  # RabbitMQ 连接配置
  rabbitmq:
    host: localhost  # 服务器地址(本地环境)
    port: 5672       # 默认端口
    username: guest  # 默认账号
    password: guest  # 默认密码
    virtual-host: /  # 默认虚拟主机
    # 生产者确认配置(确保消息发送到交换机)
    publisher-confirm-type: correlated
    # 生产者回调配置(确保消息路由到队列)
    publisher-returns: true
    # 消费者配置(手动确认消息,避免消息丢失)
    listener:
      simple:
        acknowledge-mode: manual  # 手动 ACK
        concurrency: 1  # 消费者并发数
        max-concurrency: 5  # 最大并发数
# 自定义队列、交换机、路由键配置(方便维护)
rabbitmq:
  queue:
    direct-queue: direct_queue  # Direct 队列名
    topic-queue1: topic_queue1 # Topic 队列1
    topic-queue2: topic_queue2 # Topic 队列2
    fanout-queue1: fanout_queue1 # Fanout 队列1
    fanout-queue2: fanout_queue2 # Fanout 队列2
  exchange:
    direct-exchange: direct_exchange # Direct 交换机
    topic-exchange: topic_exchange   # Topic 交换机
    fanout-exchange: fanout_exchange # Fanout 交换机
  routing-key:
    direct-key: direct_key # Direct 路由键
    topic-key1: topic.key1 # Topic 路由键1
    topic-key2: topic.key2 # Topic 路由键2
3. 核心组件配置(队列、交换机、绑定)
创建配置类,声明队列、交换机,并绑定关系:
package com.yqd.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
/**
 * RabbitMQ 核心配置:声明队列、交换机、绑定关系
 */
@Configuration
@Data
public class RabbitMQConfig {
    // 读取配置文件中的队列、交换机、路由键
    @Value("${rabbitmq.queue.direct-queue}")
    private String directQueue;
    @Value("${rabbitmq.queue.topic-queue1}")
    private String topicQueue1;
    @Value("${rabbitmq.queue.topic-queue2}")
    private String topicQueue2;
    @Value("${rabbitmq.queue.fanout-queue1}")
    private String fanoutQueue1;
    @Value("${rabbitmq.queue.fanout-queue2}")
    private String fanoutQueue2;
    @Value("${rabbitmq.exchange.direct-exchange}")
    private String directExchange;
    @Value("${rabbitmq.exchange.topic-exchange}")
    private String topicExchange;
    @Value("${rabbitmq.exchange.fanout-exchange}")
    private String fanoutExchange;
    @Value("${rabbitmq.routing-key.direct-key}")
    private String directKey;
    @Value("${rabbitmq.routing-key.topic-key1}")
    private String topicKey1;
    @Value("${rabbitmq.routing-key.topic-key2}")
    private String topicKey2;
    // ==================== 1. 声明队列(Queue)====================
    /**
     * Direct 队列:持久化、非排他、非自动删除
     */
    @Bean
    public Queue directQueue() {
        return QueueBuilder.durable(directQueue) // 持久化(重启 RabbitMQ 消息不丢失)
                .exclusive() // 非排他(多个消费者可访问)
                .autoDelete() // 非自动删除(队列不使用时不自动删除)
                .build();
    }
    /**
     * Topic 队列1
     */
    @Bean
    public Queue topicQueue1() {
        return QueueBuilder.durable(topicQueue1).build();
    }
    /**
     * Topic 队列2
     */
    @Bean
    public Queue topicQueue2() {
        return QueueBuilder.durable(topicQueue2).build();
    }
    /**
     * Fanout 队列1
     */
    @Bean
    public Queue fanoutQueue1() {
        return QueueBuilder.durable(fanoutQueue1).build();
    }
    /**
     * Fanout 队列2
     */
    @Bean
    public Queue fanoutQueue2() {
        return QueueBuilder.durable(fanoutQueue2).build();
    }
    // ==================== 2. 声明交换机(Exchange)====================
    /**
     * Direct 交换机
     */
    @Bean
    public DirectExchange directExchange() {
        return ExchangeBuilder.directExchange(directExchange)
                .durable(true) // 持久化
                .build();
    }
    /**
     * Topic 交换机
     */
    @Bean
    public TopicExchange topicExchange() {
        return ExchangeBuilder.topicExchange(topicExchange)
                .durable(true)
                .build();
    }
    /**
     * Fanout 交换机
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return ExchangeBuilder.fanoutExchange(fanoutExchange)
                .durable(true)
                .build();
    }
    // ==================== 3. 绑定(Binding):交换机 + 路由键 + 队列 ====================
    /**
     * Direct 交换机绑定 Direct 队列
     */
    @Bean
    public Binding directBinding() {
        return BindingBuilder.bind(directQueue()) // 绑定队列
                .to(directExchange()) // 绑定到交换机
                .with(directKey); // 指定路由键
    }
    /**
     * Topic 交换机绑定 Topic 队列1(路由键:topic.key1)
     */
    @Bean
    public Binding topicBinding1() {
        return BindingBuilder.bind(topicQueue1())
                .to(topicExchange())
                .with(topicKey1);
    }
    /**
     * Topic 交换机绑定 Topic 队列2(路由键:topic.#,匹配所有 topic 开头的路由键)
     */
    @Bean
    public Binding topicBinding2() {
        return BindingBuilder.bind(topicQueue2())
                .to(topicExchange())
                .with("topic.#"); // 模糊匹配:topic.key1、topic.key2、topic.key1.sub 等
    }
    /**
     * Fanout 交换机绑定 Fanout 队列1(无需路由键)
     */
    @Bean
    public Binding fanoutBinding1() {
        return BindingBuilder.bind(fanoutQueue1())
                .to(fanoutExchange());
    }
    /**
     * Fanout 交换机绑定 Fanout 队列2(无需路由键)
     */
    @Bean
    public Binding fanoutBinding2() {
        return BindingBuilder.bind(fanoutQueue2())
                .to(fanoutExchange());
    }
    // ==================== 4. 消息序列化配置(JSON 格式,方便传输对象)====================
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    // ==================== 5. RabbitTemplate 配置(生产者发送消息工具)====================
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(messageConverter()); // 序列化方式
        // 生产者确认回调(确认消息是否到达交换机)
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                System.out.println("消息发送到交换机成功,correlationData:" + correlationData);
            } else {
                System.out.println("消息发送到交换机失败,原因:" + cause);
            }
        });
        // 生产者返回回调(确认消息是否路由到队列)
        rabbitTemplate.setReturnsCallback(returned -> {
            System.out.println("消息路由到队列失败,路由键:" + returned.getRoutingKey() + ",原因:" + returned.getReplyText());
        });
        return rabbitTemplate;
    }
}
4. 消息实体类(用于传输复杂数据)
创建需要传输的消息对象(需序列化,Lombok 简化代码):
package com.yqd.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * 消息实体类(必须实现 Serializable 接口,或使用 JSON 序列化)
 */
@Data
public class MessageDTO implements Serializable {
    private Long id;         // 消息ID
    private String content;  // 消息内容
    private Date sendTime;   // 发送时间
}
5. 生产者:发送消息(3 种交换机类型案例)
创建生产者服务,实现不同交换机类型的消息发送:
package com.yqd.producer;
import com.yqd.entity.MessageDTO;
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 javax.annotation.Resource;
import java.util.Date;
/**
 * RabbitMQ 生产者:发送消息到不同交换机
 */
@Component
@Slf4j
public class RabbitMQProducer {
    @Resource
    private RabbitTemplate rabbitTemplate;
    // 读取配置文件中的交换机和路由键
    @Value("${rabbitmq.exchange.direct-exchange}")
    private String directExchange;
    @Value("${rabbitmq.exchange.topic-exchange}")
    private String topicExchange;
    @Value("${rabbitmq.exchange.fanout-exchange}")
    private String fanoutExchange;
    @Value("${rabbitmq.routing-key.direct-key}")
    private String directKey;
    @Value("${rabbitmq.routing-key.topic-key1}")
    private String topicKey1;
    @Value("${rabbitmq.routing-key.topic-key2}")
    private String topicKey2;
    /**
     * 1. 发送 Direct 类型消息(精准路由)
     */
    public void sendDirectMessage(Long id, String content) {
        // 构建消息对象
        MessageDTO message = new MessageDTO();
        message.setId(id);
        message.setContent(content);
        message.setSendTime(new Date());
        // 发送消息:交换机 + 路由键 + 消息内容
        rabbitTemplate.convertAndSend(directExchange, directKey, message);
        log.info("Direct 消息发送成功:{}", message);
    }
    /**
     * 2. 发送 Topic 类型消息(模糊路由)
     */
    public void sendTopicMessage(Long id, String content, String routingKey) {
        MessageDTO message = new MessageDTO();
        message.setId(id);
        message.setContent(content);
        message.setSendTime(new Date());
        rabbitTemplate.convertAndSend(topicExchange, routingKey, message);
        log.info("Topic 消息发送成功(路由键:{}):{}", routingKey, message);
    }
    /**
     * 3. 发送 Fanout 类型消息(广播)
     */
    public void sendFanoutMessage(Long id, String content) {
        MessageDTO message = new MessageDTO();
        message.setId(id);
        message.setContent(content);
        message.setSendTime(new Date());
        // Fanout 交换机无需路由键,设为 "" 即可
        rabbitTemplate.convertAndSend(fanoutExchange, "", message);
        log.info("Fanout 消息发送成功:{}", message);
    }
}
6. 消费者:接收并处理消息(手动 ACK)
创建消费者服务,监听队列并处理消息,手动确认避免消息丢失:
package com.yqd.consumer;
import com.rabbitmq.client.Channel;
import com.yqd.entity.MessageDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
 * RabbitMQ 消费者:监听队列,处理消息(手动 ACK)
 */
@Component
@Slf4j
public class RabbitMQConsumer {
    /**
     * 1. 监听 Direct 队列
     */
    @RabbitListener(queues = "${rabbitmq.queue.direct-queue}")
    public void handleDirectMessage(MessageDTO message, Channel channel, Message msg) throws IOException {
        try {
            // 处理消息逻辑
            log.info("Direct 消费者接收消息:{}", message);
            // 手动 ACK:确认消息已处理完成(multiple=false 表示仅确认当前消息)
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("Direct 消息处理失败:{}", e.getMessage(), e);
            // 手动 NACK:消息处理失败,重新入队(requeue=true)
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    /**
     * 2. 监听 Topic 队列1
     */
    @RabbitListener(queues = "${rabbitmq.queue.topic-queue1}")
    public void handleTopicMessage1(MessageDTO message, Channel channel, Message msg) throws IOException {
        try {
            log.info("Topic 消费者1接收消息:{}", message);
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("Topic 消息1处理失败:{}", e.getMessage(), e);
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    /**
     * 3. 监听 Topic 队列2
     */
    @RabbitListener(queues = "${rabbitmq.queue.topic-queue2}")
    public void handleTopicMessage2(MessageDTO message, Channel channel, Message msg) throws IOException {
        try {
            log.info("Topic 消费者2接收消息:{}", message);
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("Topic 消息2处理失败:{}", e.getMessage(), e);
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    /**
     * 4. 监听 Fanout 队列1
     */
    @RabbitListener(queues = "${rabbitmq.queue.fanout-queue1}")
    public void handleFanoutMessage1(MessageDTO message, Channel channel, Message msg) throws IOException {
        try {
            log.info("Fanout 消费者1接收消息:{}", message);
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("Fanout 消息1处理失败:{}", e.getMessage(), e);
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
    /**
     * 5. 监听 Fanout 队列2
     */
    @RabbitListener(queues = "${rabbitmq.queue.fanout-queue2}")
    public void handleFanoutMessage2(MessageDTO message, Channel channel, Message msg) throws IOException {
        try {
            log.info("Fanout 消费者2接收消息:{}", message);
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("Fanout 消息2处理失败:{}", e.getMessage(), e);
            channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
}
7. 测试接口(通过 HTTP 触发消息发送)
创建控制器,提供 HTTP 接口测试消息发送:
package com.yqd.controller;
import com.yqd.producer.RabbitMQProducer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
 * 测试控制器:通过 HTTP 接口触发消息发送
 */
@RestController
@RequestMapping("/rabbitmq")
public class RabbitMQTestController {
    @Resource
    private RabbitMQProducer rabbitMQProducer;
    /**
     * 测试 Direct 消息发送
     */
    @GetMapping("/direct/send")
    public String sendDirectMessage(@RequestParam Long id, @RequestParam String content) {
        rabbitMQProducer.sendDirectMessage(id, content);
        return "Direct 消息发送成功!";
    }
    /**
     * 测试 Topic 消息发送
     */
    @GetMapping("/topic/send")
    public String sendTopicMessage(@RequestParam Long id, @RequestParam String content, @RequestParam String routingKey) {
        rabbitMQProducer.sendTopicMessage(id, content, routingKey);
        return "Topic 消息发送成功(路由键:" + routingKey + ")!";
    }
    /**
     * 测试 Fanout 消息发送
     */
    @GetMapping("/fanout/send")
    public String sendFanoutMessage(@RequestParam Long id, @RequestParam String content) {
        rabbitMQProducer.sendFanoutMessage(id, content);
        return "Fanout 消息发送成功!";
    }
}
8. 启动类
package com.yqd;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RabbitmqDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(RabbitmqDemoApplication.class, args);
        System.out.println("RabbitMQ 整合项目启动成功!");
    }
}
四、测试验证(核心功能)
1. 环境准备
- 启动 RabbitMQ 服务(本地环境:rabbitmq-server start,Windows 直接启动服务)。
- 访问 RabbitMQ 管理界面:http://localhost:15672,用guest/guest登录,可看到配置的队列、交换机、绑定关系。
- 启动 SpringBoot 项目。
2. 测试 Direct 交换机(精准路由)
- 访问接口:http://localhost:8080/rabbitmq/direct/send?id=1&content=Direct消息测试
- 预期结果:
- 控制台打印 “Direct 消息发送成功” 和 “Direct 消费者接收消息”。
- 管理界面中 direct_queue队列的消息被消费(Ready 数为 0)。
 
3. 测试 Topic 交换机(模糊路由)
- 
测试 1:访问 http://localhost:8080/rabbitmq/topic/send?id=2&content=Topic消息1&routingKey=topic.key1- 预期:Topic 消费者 1 和消费者 2 都接收消息(topic.key1匹配topic.key1和topic.#)。
 
- 预期:Topic 消费者 1 和消费者 2 都接收消息(
- 
测试 2:访问 http://localhost:8080/rabbitmq/topic/send?id=3&content=Topic消息2&routingKey=topic.key2.sub- 预期:仅 Topic 消费者 2 接收消息(topic.key2.sub仅匹配topic.#)。
 
- 预期:仅 Topic 消费者 2 接收消息(
4. 测试 Fanout 交换机(广播)
- 访问接口:http://localhost:8080/rabbitmq/fanout/send?id=4&content=Fanout消息测试
- 预期结果:Fanout 消费者 1 和消费者 2 都接收消息(广播到所有绑定队列)。
5. 测试消息可靠性(手动 ACK)
- 故意在消费者中抛出异常(如 int i = 1/0),重新发送消息。
- 预期结果:消息处理失败后重新入队(Ready 数反复增加),控制台打印错误日志,不会丢失消息。
五、关键注意事项
- 消息持久化:队列和交换机都需设置 durable=true,消息发送时需指定deliveryMode=PERSISTENT(SpringBoot 默认持久化),确保 RabbitMQ 重启后消息不丢失。
- 手动 ACK:acknowledge-mode: manual避免消费者崩溃导致消息丢失,处理完成后必须调用basicAck,失败时调用basicNack重新入队或死信队列。
- 生产者确认:publisher-confirm-type: correlated和publisher-returns: true确保消息到达交换机并路由到队列,避免生产者发送消息丢失。
- 死信队列:复杂场景下可配置死信队列(DLX),处理无法消费的消息(如重试多次仍失败的消息),避免队列阻塞。
六、扩展场景
- 延迟队列:实现定时任务(如订单超时取消),通过 x-message-ttl(消息过期时间)和死信队列组合实现。
- 集群部署:生产环境需部署 RabbitMQ 集群,配置 spring.rabbitmq.addresses多个节点地址,提高可用性。
- 消息幂等性:消费者需处理重复消息(如网络重试导致),可通过消息 ID 去重(如存入 Redis)。

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号