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号