一、核心设计思路
先明确 RabbitMQ 保证消息安全的核心链路:


二、环境准备
1. 依赖引入(pom.xml)
java
<dependencies>
<!-- Spring Boot 基础 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- RabbitMQ 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- 工具类(JSON、UUID) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 配置文件(application.yml)
java
spring:
# RabbitMQ 基础配置
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
# 连接池配置
connection-timeout: 10000
# 发送确认配置
publisher-confirm-type: correlated # 开启Confirm确认(异步回调)
publisher-returns: true # 开启Return回退(消息到交换机但未到队列时回调)
template:
mandatory: true # 强制触发Return回调
# 消费端配置
listener:
simple:
acknowledge-mode: manual # 手动ACK(关键:禁用自动确认)
concurrency: 1 # 最小消费线程数
max-concurrency: 5 # 最大消费线程数
retry:
enabled: true # 开启本地重试
max-attempts: 3 # 本地最大重试次数(1次正常消费+3次重试)
initial-interval: 1000ms # 首次重试间隔
multiplier: 2 # 重试间隔倍数(1s→2s→4s)
max-interval: 5000ms # 最大重试间隔
default-requeue-rejected: false # 消费失败不重新入队(避免无限循环)
三、核心代码实现
1. 队列 / 交换机 / 死信配置(RabbitMQConfig.java)
java
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitMQConfig {
// 核心队列/交换机名称
public static final String BUSINESS_QUEUE = "business.queue";
public static final String BUSINESS_EXCHANGE = "business.exchange";
public static final String BUSINESS_ROUTING_KEY = "business.key";
// 死信相关名称
public static final String DEAD_LETTER_QUEUE = "dead.letter.queue";
public static final String DEAD_LETTER_EXCHANGE = "dead.letter.exchange";
public static final String DEAD_LETTER_ROUTING_KEY = "dead.letter.key";
// 1. 声明死信交换机
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange(DEAD_LETTER_EXCHANGE, true, false);
}
// 2. 声明死信队列
@Bean
public Queue deadLetterQueue() {
return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
}
// 3. 绑定死信队列到死信交换机
@Bean
public Binding deadLetterBinding() {
return BindingBuilder.bind(deadLetterQueue())
.to(deadLetterExchange())
.with(DEAD_LETTER_ROUTING_KEY);
}
// 4. 声明业务队列(绑定死信交换机)
@Bean
public Queue businessQueue() {
Map<String, Object> args = new HashMap<>();
// 绑定死信交换机
args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
// 绑定死信路由键
args.put("x-dead-letter-routing-key", DEAD_LETTER_ROUTING_KEY);
// 队列消息过期时间(可选,此处不设置,由消费失败触发死信)
// args.put("x-message-ttl", 60000);
return QueueBuilder.durable(BUSINESS_QUEUE).withArguments(args).build();
}
// 5. 声明业务交换机
@Bean
public DirectExchange businessExchange() {
return new DirectExchange(BUSINESS_EXCHANGE, true, false);
}
// 6. 绑定业务队列到业务交换机
@Bean
public Binding businessBinding() {
return BindingBuilder.bind(businessQueue())
.to(businessExchange())
.with(BUSINESS_ROUTING_KEY);
}
}
2. 生产者(消息发送 + 确认回调)(ProducerService.java)
java
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.UUID;
@Service
public class ProducerService {
private static final Logger log = LoggerFactory.getLogger(ProducerService.class);
@Resource
private RabbitTemplate rabbitTemplate;
// 初始化:设置发送确认和回退回调
@PostConstruct
public void initRabbitTemplate() {
// 1. Confirm回调:消息到达交换机触发(无论成功/失败)
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
String msgId = correlationData.getId();
if (ack) {
log.info("消息[{}]成功发送到交换机", msgId);
} else {
log.error("消息[{}]发送到交换机失败,原因:{}", msgId, cause);
// 此处可添加失败重试/告警/入库补偿逻辑
}
});
// 2. Return回调:消息到达交换机但未路由到队列触发
rabbitTemplate.setReturnsCallback(returned -> {
String msgId = returned.getMessage().getMessageProperties().getMessageId();
log.error("消息[{}]路由到队列失败,路由键:{},回复码:{},原因:{}",
msgId, returned.getRoutingKey(), returned.getReplyCode(), returned.getReplyText());
// 此处可添加重发/入库补偿逻辑
});
}
/**
* 发送业务消息
* @param data 业务数据(示例:订单ID)
*/
public void sendBusinessMessage(String data) {
// 1. 生成唯一消息ID(用于幂等/追踪)
String msgId = UUID.randomUUID().toString();
// 2. 构建消息(转JSON)
String msg = JSON.toJSONString(data);
// 3. 构建CorrelationData(用于Confirm回调关联消息)
CorrelationData correlationData = new CorrelationData(msgId);
// 4. 发送消息
rabbitTemplate.convertAndSend(
RabbitMQConfig.BUSINESS_EXCHANGE,
RabbitMQConfig.BUSINESS_ROUTING_KEY,
msg,
message -> {
// 设置消息ID(用于消费端幂等)
message.getMessageProperties().setMessageId(msgId);
return message;
},
correlationData
);
log.info("消息[{}]已发送(待确认),内容:{}", msgId, data);
}
}
3. 消费者(手动 ACK + 重试 + 死信)(ConsumerService.java)
java
import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class ConsumerService {
private static final Logger log = LoggerFactory.getLogger(ConsumerService.class);
/**
* 消费业务消息(手动ACK+本地重试)
*/
@RabbitListener(queues = RabbitMQConfig.BUSINESS_QUEUE)
public void consumeBusinessMessage(Message message, Channel channel) throws IOException {
String msgId = message.getMessageProperties().getMessageId();
String msgContent = new String(message.getBody());
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.info("开始消费消息[{}],内容:{}", msgId, msgContent);
// 模拟业务处理(此处可替换为真实业务逻辑)
String orderId = JSON.parseObject(msgContent).toString();
if ("error_order".equals(orderId)) {
// 模拟业务处理失败(触发重试)
throw new RuntimeException("订单处理失败:库存不足");
}
// 1. 业务处理成功:手动确认ACK(关键:仅成功时确认)
channel.basicAck(deliveryTag, false);
log.info("消息[{}]消费成功,已确认ACK", msgId);
} catch (Exception e) {
log.error("消息[{}]消费失败,准备投递死信队列,原因:{}", msgId, e.getMessage());
// 2. 业务处理失败:拒绝消息并投递到死信队列(本地重试3次后执行此逻辑)
// basicNack参数说明:
// - deliveryTag:消息唯一标识
// - multiple:是否批量拒绝
// - requeue:是否重新入队(false=直接投递死信)
channel.basicNack(deliveryTag, false, false);
}
}
/**
* 消费死信消息(归档/告警)
*/
@RabbitListener(queues = RabbitMQConfig.DEAD_LETTER_QUEUE)
public void consumeDeadLetterMessage(Message message, Channel channel) throws IOException {
String msgId = message.getMessageProperties().getMessageId();
String msgContent = new String(message.getBody());
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.warn("处理死信消息[{}],内容:{},开始归档/告警", msgId, msgContent);
// 此处添加死信处理逻辑:入库归档、发送告警、人工介入等
// 示例:插入死信日志表
// deadLetterMapper.insert(msgId, msgContent, "消费重试3次失败");
// 确认死信消费成功
channel.basicAck(deliveryTag, false);
log.info("死信消息[{}]归档成功", msgId);
} catch (Exception e) {
log.error("死信消息[{}]处理失败", msgId, e);
// 死信处理失败:直接拒绝(避免死循环)
channel.basicNack(deliveryTag, false, false);
}
}
}
4. 测试类(TestRabbitMQ.java)
java
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class TestRabbitMQ {
@Resource
private ProducerService producerService;
// 测试正常消息发送
@Test
public void testSendNormalMessage() {
producerService.sendBusinessMessage("normal_order_123456");
// 休眠3秒,等待消费完成
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 测试失败消息(触发重试+死信)
@Test
public void testSendErrorMessage() {
producerService.sendBusinessMessage("error_order");
// 休眠10秒,等待重试+死信处理完成
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

