在分布式系统中,消息可靠性是核心需求之一。RabbitMQ 提供的消息确认机制(Message Acknowledgement),能确保消费者准确接收并处理消息,避免因消费者异常导致消息丢失。
一、消息确认机制的核心原理
1.1 为什么需要消息确认?
默认情况下,RabbitMQ 发送消息给消费者后,会立即从队列中删除消息。但实际场景中可能存在以下问题:

- 消费者接收到消息后,尚未处理就宕机;
- 消费者处理消息时抛出异常,导致业务逻辑未完成。
若不进行确认,这些未处理的消息会被直接删除,造成数据丢失。消息确认机制通过「消费者显式通知 RabbitMQ 消息处理状态」,解决这一问题:
- RabbitMQ 发送消息后,不会立即删除,而是标记为「已投递但未确认(Unacked)」;
- 消费者处理完成后,发送确认信号(ACK/NACK),RabbitMQ 才会删除消息或重新投递。
1.2 两种确认模式
RabbitMQ 的消息确认通过消费者订阅队列时的 autoAck 参数控制,分为自动确认 和手动确认两种模式:
| 确认模式 | autoAck 值 |
核心逻辑 | 适用场景 |
|---|---|---|---|
| 自动确认 | true |
消息投递到消费者后,RabbitMQ 立即标记为确认并删除,无论消费者是否处理完成。 | 对消息可靠性要求低的场景(如日志采集) |
| 手动确认 | false |
消费者需显式调用 API 发送确认信号,RabbitMQ 收到后才删除消息。 | 对消息可靠性要求高的场景(如订单、支付) |
1.3 队列消息的两种状态
当 autoAck=false 时,RabbitMQ 队列中的消息会分为两种状态:
- Ready(待投递):等待分配给消费者的消息;
- Unacked(未确认):已投递到消费者,但未收到确认信号的消息。
若消费者断开连接且未确认消息,RabbitMQ 会将「Unacked」状态的消息重新标记为「Ready」,分配给其他消费者,确保消息不丢失。

从RabbitMQ的Web管理平台上, 也可以看到当前队列中Ready状态和Unacked状态的消息数

二、手动确认的三种核心 API
当使用手动确认模式(
autoAck=false)时,消费者需通过 Channel 调用以下 API 发送确认信号,控制消息的后续处理逻辑:
2.1 肯定确认:basicAck
- 作用:通知 RabbitMQ「消息已成功处理,可删除」;
- 方法签名 :
void basicAck(long deliveryTag, boolean multiple); - 参数说明 :
deliveryTag:消息的唯一标识(每个 Channel 独立维护,单调递增的 64 位长整型);multiple:是否批量确认。true表示确认「当前deliveryTag及之前所有未确认的消息」,false表示仅确认当前deliveryTag的消息。
示例:
java
// 单条确认:仅确认 deliveryTag=8 的消息
channel.basicAck(8, false);
// 批量确认:确认 deliveryTag ≤8 的所有消息
channel.basicAck(8, true);
2.2 否定确认(单条):basicReject
- 作用:通知 RabbitMQ「消息处理失败,拒绝接收」,可指定是否重新投递;
- 方法签名 :
void basicReject(long deliveryTag, boolean requeue); - 参数说明 :
deliveryTag:消息唯一标识;requeue:是否重新入队。true表示将消息放回队列,等待重新投递;false表示直接丢弃消息(若配置死信队列,消息会进入死信队列)。
局限:一次只能拒绝一条消息,无法批量处理。
示例:
java
// 拒绝处理 deliveryTag=5 的消息,且不重新入队(直接丢弃)
channel.basicReject(5, false);
2.3 否定确认(批量):basicNack
- 作用 :功能与
basicReject一致,但支持批量拒绝消息; - 方法签名 :
void basicNack(long deliveryTag, boolean multiple, boolean requeue); - 参数说明 :
deliveryTag:消息唯一标识;multiple:是否批量拒绝。true表示拒绝「当前deliveryTag及之前所有未确认的消息」;requeue:是否重新入队。
示例:
java
// 批量拒绝 deliveryTag ≤10 的所有消息,且重新入队
channel.basicNack(10, true, true);
三、Spring Boot 中的消息确认配置与代码示例
RabbitMQ 消息确认机制是保障消息可靠性的核心手段,不同确认模式适用于不同业务场景。
Spring Boot 通过
spring-boot-starter-amqp封装了 RabbitMQ 客户端,提供AcknowledgeMode枚举类,支持三种确认策略。
NONE:追求吞吐量,不关心消息丢失;AUTO:简单易用,但异常时会无限重试;MANUAL:最可靠,完全控制消息生命周期,适用于核心业务。
3.1 策略一:AcknowledgeMode.NONE(无确认)
- 核心逻辑:消息投递到消费者后,RabbitMQ 立即自动确认,无论消费者是否处理成功;
- 本质 :等同于原生客户端的
autoAck=true,不保证消息可靠性; - 适用场景:消息丢失无影响的场景(如非核心日志)。
3.1.1 配置文件
在 application.yml 中配置确认模式:
yaml
spring:
rabbitmq:
addresses: amqp://study:study@110.41.51.65:5672/bite
listener:
simple:
acknowledge-mode: none # 无确认模式
3.1.2 代码实现(生产者+消费者)
- 生产者:发送消息到队列;
- 消费者:处理消息时抛出异常,但 RabbitMQ 已自动确认,消息会丢失。
java
// 1. 常量类:定义交换机、队列名称
public class Constant {
public static final String ACK_EXCHANGE = "ack_exchange";
public static final String ACK_QUEUE = "ack_queue";
}
// 2. 配置类:声明交换机、队列及绑定关系
@Configuration
public class RabbitConfig {
// 声明交换机(topic类型,持久化)
@Bean
public Exchange ackExchange() {
return ExchangeBuilder.topicExchange(Constant.ACK_EXCHANGE)
.durable(true)
.build();
}
// 声明队列(持久化)
@Bean
public Queue ackQueue() {
return QueueBuilder.durable(Constant.ACK_QUEUE).build();
}
// 绑定交换机与队列(路由键=ack)
@Bean
public Binding ackBinding(Exchange exchange, Queue queue) {
return BindingBuilder.bind(queue)
.to(exchange)
.with("ack")
.noargs();
}
}
// 3. 生产者:发送消息
@RestController
@RequestMapping("/producer")
public class ProducerController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/ack/none")
public String sendNoneAckMsg() {
String msg = "测试无确认模式";
rabbitTemplate.convertAndSend(Constant.ACK_EXCHANGE, "ack", msg);
return "消息发送成功";
}
}
// 4. 消费者:处理消息时抛出异常
@Component
public class AckListener {
@RabbitListener(queues = Constant.ACK_QUEUE)
public void listenNoneAckMsg(Message message) {
String msg = new String(message.getBody());
System.out.println("接收到消息:" + msg);
// 模拟处理失败
int num = 3 / 0;
System.out.println("处理完成"); // 不会执行
}
}
3.1.3 运行结果
- 消费者抛出异常,但 RabbitMQ 已自动确认消息,队列中
Ready和Unacked均为 0,消息丢失; - 管理界面显示:消息已被删除,无残留。
3.2 策略二:AcknowledgeMode.AUTO(自动确认)
- 核心逻辑 :Spring 自动管理确认流程:
- 若消费者处理消息无异常,自动调用
basicAck确认; - 若抛出异常,不确认消息,RabbitMQ 会不断重新投递消息;
- 若消费者处理消息无异常,自动调用
- 风险 :异常未修复时,消息会无限重试,导致
Unacked消息积压。
3.2.1 配置文件
yaml
spring:
rabbitmq:
addresses: amqp://study:study@110.41.51.65:5672/bite
listener:
simple:
acknowledge-mode: auto # 自动确认模式
3.2.2 运行结果(复用上述代码)
- 消费者抛出异常,Spring 不发送确认信号;
- RabbitMQ 不断重新投递消息,控制台持续打印异常日志;
- 管理界面显示
Unacked状态的消息数为 1,且持续增长(每次重试生成新的deliveryTag)。
3.3 策略三:AcknowledgeMode.MANUAL(手动确认)
- 核心逻辑 :消费者需显式调用
basicAck/basicNack确认消息,完全控制消息生命周期; - 优势:最可靠的确认模式,适用于核心业务(如订单、支付)。
3.3.1 配置文件
yaml
spring:
rabbitmq:
addresses: amqp://study:study@110.41.51.65:5672/bite
listener:
simple:
acknowledge-mode: manual # 手动确认模式
3.3.2 消费者代码(手动确认/拒绝)
java
@Component
public class AckListener {
@RabbitListener(queues = Constant.ACK_QUEUE)
public void listenManualAckMsg(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 1. 处理消息
String msg = new String(message.getBody(), "UTF-8");
System.out.println("接收到消息:" + msg + ",deliveryTag:" + deliveryTag);
// 模拟业务逻辑(无异常则确认)
System.out.println("业务处理完成");
// 2. 手动确认:单条确认
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 3. 处理失败:拒绝消息,不重新入队(可根据业务决定是否入队)
System.err.println("消息处理失败,拒绝接收");
channel.basicNack(deliveryTag, false, false);
}
}
}
3.3.3 运行结果
- 正常情况 :消费者处理无异常,调用
basicAck,RabbitMQ 删除消息,队列Ready和Unacked均为 0; - 异常情况 :消费者抛出异常,调用
basicNack拒绝消息,消息被丢弃(或进入死信队列),无无限重试问题。
四、常见问题与实践
4.1 如何避免消息重复消费?
- 原因 :手动确认模式下,消费者处理完成但未发送
ACK就宕机,RabbitMQ 会重新投递消息,导致重复消费; - 解决方案 :
- 消息携带唯一 ID(如订单号、UUID);
- 消费者处理前,先查询数据库/缓存,判断消息是否已处理;
- 业务逻辑设计为「幂等操作」(如
INSERT ... ON DUPLICATE KEY UPDATE)。
4.2 如何处理大量 Unacked 消息?
- 原因:消费者处理速度慢,或未及时发送确认信号;
- 解决方案 :
- 配置
prefetch参数(限流):控制消费者每次预取的消息数量,避免一次性接收过多消息; - 水平扩展消费者:增加消费者实例,分担处理压力;
- 排查消费者处理逻辑:优化慢查询、减少同步操作。
- 配置
4.3 手动确认时,deliveryTag 为什么重复?
- 原因 :
deliveryTag是每个 Channel 独立维护的计数器,若消费者使用多个 Channel,不同 Channel 的deliveryTag会重新从 1 开始计数; - 注意 :确认消息时,必须使用消息投递时的 Channel,否则会报
Unknown delivery tag错误。