前言
在 RabbitMQ 中,消息投递成功 不等于业务处理成功。
生产者把消息投递到 RabbitMQ,只解决了「消息进入 Broker」的问题;RabbitMQ 把消息推给消费者,也只解决了「消息到达消费者进程」的问题。真正困难的是:
消费者拿到消息后,业务逻辑执行到一半宕机怎么办?
消费者抛异常了,消息是丢弃、重试,还是进入死信队列?
控制台里的
Ready和Unacked到底在表达什么?
这就是 Message Acknowledgement,消息确认机制要解决的问题。
RabbitMQ 官方文档也明确区分了两类确认机制:
| 方向 | 机制 | 作用 |
|---|---|---|
| Producer -> RabbitMQ | Publisher Confirms | 确认消息是否成功到达 Broker |
| RabbitMQ -> Consumer | Consumer Acknowledgements | 确认消息是否被消费者成功处理 |
本文只聚焦后者:消费端确认机制。
一、核心机制解析:自动确认与手动确认
RabbitMQ 在消费者订阅队列时,会通过 autoAck 决定确认模式。
channel.basicConsume(queueName, autoAck, consumer);
| 模式 | autoAck |
RabbitMQ 何时删除消息 | 可靠性 | 适用场景 |
|---|---|---|---|---|
| 自动确认 | true |
消息一投递给消费者就视为成功 | 低 | 日志、指标、允许少量丢失的异步任务 |
| 手动确认 | false |
消费者显式调用 basicAck 后删除 |
高 | 订单、支付、库存、积分、通知等核心链路 |
自动确认本质上是 fire-and-forget。
RabbitMQ 只负责把消息发出去,不关心消费者有没有真正处理完成。
1. 自动确认:吞吐高,但容易丢消息
自动确认流程:
Producer -> Exchange -> Queue -> Consumer
|
| autoAck=true
v
RabbitMQ 立即删除消息
如果消费者收到消息后立刻宕机:
- RabbitMQ 已经认为消息成功;
- 队列中不会再保留该消息;
- 消息直接丢失。
适合:
- 可丢失的监控埋点;
- 非核心日志;
- 消费端处理极快且幂等成本低的场景。
不适合:
- 金额变更;
- 库存扣减;
- 订单状态流转;
- 任何「丢一条就要查事故」的系统。
1.1 自动确认代码实现
配置交换机和队列,建立绑定关系
java
public class Constants {
public static final String ACK_QUEUE = "ack.queue";
public static final String ACK_EXCHANGE = "ack.exchange";
}
java
import com.amadeus.rabbitextensiondemo.constant.Constants;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
//消息确认
@Bean("ackQueue")
public Queue RabbitMQConfig(){
return QueueBuilder.durable(Constants.ACK_QUEUE).build();
}
@Bean("directExchange")
public DirectExchange directExchange(){
return ExchangeBuilder.directExchange(Constants.ACK_EXCHANGE).build();
}
@Bean("ackBinding")
public Binding ackBinding(@Qualifier("directExchange") DirectExchange directExchange, @Qualifier("ackQueue") Queue queue){
return BindingBuilder.bind(queue).to(directExchange).with("ack");
}
}
设置生产者和消费者
producer
java
import com.amadeus.rabbitextensiondemo.constant.Constants;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/producer")
public class ProducerController {
@Autowired
private RabbitTemplate rabbitTemplate;
@RequestMapping("/ack")
public String ack(){
rabbitTemplate.convertAndSend(Constants.ACK_EXCHANGE,"ack","hello cheems");
System.out.println("发送成功");
return "success";
}
}
listener
java
import com.amadeus.rabbitextensiondemo.constant.Constants;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class AckListener {
//自动确认auto
@RabbitListener(queues = Constants.ACK_QUEUE)
public void handMessage(Message message) {
try {
System.out.printf("接收到消息: %s\n", new String(message.getBody(), "UTF-8"));
System.out.println("业务逻辑处理");
System.out.println("业务处理完成");
} catch (Exception e) {
System.err.println("消息处理失败: " + e.getMessage());
}
}
}
1.2 测试

消息一经发出就立刻ack了,
如果消费者处理消息异常时会如何呢? 我们不妨在自动确认的机制下尝试~

未成功被消费的消息就好似直接被丢弃了, (被遗弃消息: 我还没上车呐~ )
总结: 自动确认不能保证消息的可靠性
2. 手动确认:消费端可靠性的核心
手动确认模式下,消息状态会拆成两类:
| 控制台状态 | 含义 |
|---|---|
Ready |
还在队列中,等待投递给消费者 |
Unacked |
已经投递给消费者,但 RabbitMQ 还没收到确认信号 |
手动确认流程:
[Ready]
|
| RabbitMQ 投递给 Consumer
v
[Unacked]
|
| Consumer 处理成功 -> basicAck
v
[Deleted]
[Unacked]
|
| Consumer 断开连接 / Channel 关闭
v
[Requeue -> Ready]
Unacked 不是消息丢了,而是消息正在消费者手里"处理中"。
如果消费者挂掉,RabbitMQ 会把未确认消息重新入队,等待下一次投递。
2.1 手动确认代码实现
生产者代码基本无需处理,只需要调整两个地方 ,分别是确认策略和消费代码
yml文件配置信息
yml
spring:
application:
name: rabbit-extensions-demo
rabbitmq:
addresses: amqp://"账号名":"密码"@"RabbitMQ服务器ip地址端口号"/"虚拟机名"
listener:
simple:
# acknowledge-mode: none #消息接收确认
# acknowledge-mode: auto #消息接收确认
acknowledge-mode: manual #消息接收确认: 手动确认
prefetch: 1 # 每次从队列中获取消息的条数
消费者处理
java
//手动确认
@RabbitListener(queues = Constants.ACK_QUEUE)
public void handMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//消费者逻辑
System.out.printf("接收到消息: %s, deliveryTag: %d \n", new String(message.getBody(),"UTF-8"), message.getMessageProperties().getDeliveryTag());
//进行业务逻辑处理
System.out.println("业务逻辑处理");
int num = 3/0;
System.out.println("业务处理完成");
//肯定确认
channel.basicAck(deliveryTag,false);
} catch (Exception e) {
//否定确认
channel.basicNack(deliveryTag, false, true);
}
}
按照这样的修改,发送了一场就会走否定确认,消息消费失败就不断尝试加入队列中
2.2 测试

如何限制消息消费的次数,防止日志刷屏, 避免消费者一直被占用呢? 这在后面会提及
二、Ready 与 Unacked:控制台状态深度剖析
RabbitMQ Management 控制台中,队列经常看到这几个数字:
| 指标 | 解释 | 常见原因 |
|---|---|---|
Ready 很高 |
消息堆在队列里,还没投递 | 消费者数量不足、消费太慢、没有消费者 |
Unacked 很高 |
消息已投递但未确认 | 业务处理慢、忘记 ack、线程阻塞、prefetch 过大 |
Ready=0, Unacked>0 |
消息都被消费者拿走了,但还没处理完 | 消费者可能卡住或确认逻辑异常 |
Ready 和 Unacked 都高 |
既有堆积,也有大量处理中消息 | 消费能力不足,需扩容或限流 |
一个非常典型的故障现场
Ready: 0
Unacked: 1000
Consumers: 1
这通常意味着:
- 消费者一次性拿走了大量消息;
- 没有及时
basicAck; - 或者业务逻辑卡住;
- RabbitMQ 不能继续安全删除这些消息。
此时不要第一反应重启 RabbitMQ。
更应该先检查消费者线程、日志异常、数据库慢查询、第三方接口超时,以及 prefetch 设置。
#mermaid-svg-2Csin9UIO7bR36aT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2Csin9UIO7bR36aT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2Csin9UIO7bR36aT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2Csin9UIO7bR36aT .error-icon{fill:#552222;}#mermaid-svg-2Csin9UIO7bR36aT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2Csin9UIO7bR36aT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2Csin9UIO7bR36aT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2Csin9UIO7bR36aT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2Csin9UIO7bR36aT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2Csin9UIO7bR36aT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2Csin9UIO7bR36aT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2Csin9UIO7bR36aT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2Csin9UIO7bR36aT .marker.cross{stroke:#333333;}#mermaid-svg-2Csin9UIO7bR36aT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2Csin9UIO7bR36aT p{margin:0;}#mermaid-svg-2Csin9UIO7bR36aT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2Csin9UIO7bR36aT .cluster-label text{fill:#333;}#mermaid-svg-2Csin9UIO7bR36aT .cluster-label span{color:#333;}#mermaid-svg-2Csin9UIO7bR36aT .cluster-label span p{background-color:transparent;}#mermaid-svg-2Csin9UIO7bR36aT .label text,#mermaid-svg-2Csin9UIO7bR36aT span{fill:#333;color:#333;}#mermaid-svg-2Csin9UIO7bR36aT .node rect,#mermaid-svg-2Csin9UIO7bR36aT .node circle,#mermaid-svg-2Csin9UIO7bR36aT .node ellipse,#mermaid-svg-2Csin9UIO7bR36aT .node polygon,#mermaid-svg-2Csin9UIO7bR36aT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2Csin9UIO7bR36aT .rough-node .label text,#mermaid-svg-2Csin9UIO7bR36aT .node .label text,#mermaid-svg-2Csin9UIO7bR36aT .image-shape .label,#mermaid-svg-2Csin9UIO7bR36aT .icon-shape .label{text-anchor:middle;}#mermaid-svg-2Csin9UIO7bR36aT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2Csin9UIO7bR36aT .rough-node .label,#mermaid-svg-2Csin9UIO7bR36aT .node .label,#mermaid-svg-2Csin9UIO7bR36aT .image-shape .label,#mermaid-svg-2Csin9UIO7bR36aT .icon-shape .label{text-align:center;}#mermaid-svg-2Csin9UIO7bR36aT .node.clickable{cursor:pointer;}#mermaid-svg-2Csin9UIO7bR36aT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2Csin9UIO7bR36aT .arrowheadPath{fill:#333333;}#mermaid-svg-2Csin9UIO7bR36aT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2Csin9UIO7bR36aT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2Csin9UIO7bR36aT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2Csin9UIO7bR36aT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2Csin9UIO7bR36aT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2Csin9UIO7bR36aT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2Csin9UIO7bR36aT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2Csin9UIO7bR36aT .cluster text{fill:#333;}#mermaid-svg-2Csin9UIO7bR36aT .cluster span{color:#333;}#mermaid-svg-2Csin9UIO7bR36aT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2Csin9UIO7bR36aT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2Csin9UIO7bR36aT rect.text{fill:none;stroke-width:0;}#mermaid-svg-2Csin9UIO7bR36aT .icon-shape,#mermaid-svg-2Csin9UIO7bR36aT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2Csin9UIO7bR36aT .icon-shape p,#mermaid-svg-2Csin9UIO7bR36aT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2Csin9UIO7bR36aT .icon-shape .label rect,#mermaid-svg-2Csin9UIO7bR36aT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2Csin9UIO7bR36aT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2Csin9UIO7bR36aT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2Csin9UIO7bR36aT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} basicAck
basicReject / basicNack, requeue=true
basicReject / basicNack, requeue=false 且配置 DLX
basicReject / basicNack, requeue=false 且未配置 DLX
Consumer 断开 / Channel 关闭
Producer 发送消息
Exchange 路由
Queue
Ready: 等待投递
RabbitMQ 投递给 Consumer
Unacked: 已投递, 未确认
消息删除
Dead Letter Exchange
Dead Letter Queue
消息丢弃
三、底层 API 详解
RabbitMQ Java Client 中,消费端确认主要有三个方法:
java
channel.basicAck(long deliveryTag, boolean multiple);
channel.basicReject(long deliveryTag, boolean requeue);
channel.basicNack(long deliveryTag, boolean multiple, boolean requeue);
1. deliveryTag:不是消息 ID,而是 Channel 内的投递编号
deliveryTag 是 RabbitMQ 给每次投递生成的编号。
| 特性 | 说明 |
|---|---|
| 单调递增 | 同一个 Channel 内从小到大递增 |
| Channel 级别唯一 | 不同 Channel 可以出现相同 deliveryTag |
| 必须同 Channel 确认 | A Channel 收到的消息,不能用 B Channel ack |
| 用于确认投递 | basicAck / basicReject / basicNack 都依赖它 |
错误示例:
java
// message 是 channel-1 收到的
// 却用 channel-2 ack
channel2.basicAck(deliveryTag, false);
后果:
unknown delivery tag
channel closed
deliveryTag 不是业务消息 ID。
业务去重请使用业务唯一键,例如
orderNo、eventId、messageId。
2. basicAck:肯定确认
channel.basicAck(deliveryTag, multiple);
含义:
告诉 RabbitMQ:这条消息已经被我成功处理,可以删除了。
| 参数 | 类型 | 含义 |
|---|---|---|
deliveryTag |
long |
要确认的投递编号 |
multiple |
boolean |
是否批量确认 |
multiple=false
只确认当前这一条。
java
channel.basicAck(deliveryTag, false);
适合:
- 单条业务处理;
- 每条消息独立事务;
- 失败隔离要求高。
multiple=true
确认当前 deliveryTag 以及之前所有未确认的消息。
channel.basicAck(deliveryTag, true);
批量确认示意:
Unacked tags: 5, 6, 7, 8
basicAck(8, true)
Result:
5, 6, 7, 8 全部被确认并删除
图表占位:
#mermaid-svg-43PcZyr4EgTm7ueu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-43PcZyr4EgTm7ueu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-43PcZyr4EgTm7ueu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-43PcZyr4EgTm7ueu .error-icon{fill:#552222;}#mermaid-svg-43PcZyr4EgTm7ueu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-43PcZyr4EgTm7ueu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-43PcZyr4EgTm7ueu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-43PcZyr4EgTm7ueu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-43PcZyr4EgTm7ueu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-43PcZyr4EgTm7ueu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-43PcZyr4EgTm7ueu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-43PcZyr4EgTm7ueu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-43PcZyr4EgTm7ueu .marker.cross{stroke:#333333;}#mermaid-svg-43PcZyr4EgTm7ueu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-43PcZyr4EgTm7ueu p{margin:0;}#mermaid-svg-43PcZyr4EgTm7ueu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-43PcZyr4EgTm7ueu .cluster-label text{fill:#333;}#mermaid-svg-43PcZyr4EgTm7ueu .cluster-label span{color:#333;}#mermaid-svg-43PcZyr4EgTm7ueu .cluster-label span p{background-color:transparent;}#mermaid-svg-43PcZyr4EgTm7ueu .label text,#mermaid-svg-43PcZyr4EgTm7ueu span{fill:#333;color:#333;}#mermaid-svg-43PcZyr4EgTm7ueu .node rect,#mermaid-svg-43PcZyr4EgTm7ueu .node circle,#mermaid-svg-43PcZyr4EgTm7ueu .node ellipse,#mermaid-svg-43PcZyr4EgTm7ueu .node polygon,#mermaid-svg-43PcZyr4EgTm7ueu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-43PcZyr4EgTm7ueu .rough-node .label text,#mermaid-svg-43PcZyr4EgTm7ueu .node .label text,#mermaid-svg-43PcZyr4EgTm7ueu .image-shape .label,#mermaid-svg-43PcZyr4EgTm7ueu .icon-shape .label{text-anchor:middle;}#mermaid-svg-43PcZyr4EgTm7ueu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-43PcZyr4EgTm7ueu .rough-node .label,#mermaid-svg-43PcZyr4EgTm7ueu .node .label,#mermaid-svg-43PcZyr4EgTm7ueu .image-shape .label,#mermaid-svg-43PcZyr4EgTm7ueu .icon-shape .label{text-align:center;}#mermaid-svg-43PcZyr4EgTm7ueu .node.clickable{cursor:pointer;}#mermaid-svg-43PcZyr4EgTm7ueu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-43PcZyr4EgTm7ueu .arrowheadPath{fill:#333333;}#mermaid-svg-43PcZyr4EgTm7ueu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-43PcZyr4EgTm7ueu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-43PcZyr4EgTm7ueu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-43PcZyr4EgTm7ueu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-43PcZyr4EgTm7ueu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-43PcZyr4EgTm7ueu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-43PcZyr4EgTm7ueu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-43PcZyr4EgTm7ueu .cluster text{fill:#333;}#mermaid-svg-43PcZyr4EgTm7ueu .cluster span{color:#333;}#mermaid-svg-43PcZyr4EgTm7ueu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-43PcZyr4EgTm7ueu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-43PcZyr4EgTm7ueu rect.text{fill:none;stroke-width:0;}#mermaid-svg-43PcZyr4EgTm7ueu .icon-shape,#mermaid-svg-43PcZyr4EgTm7ueu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-43PcZyr4EgTm7ueu .icon-shape p,#mermaid-svg-43PcZyr4EgTm7ueu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-43PcZyr4EgTm7ueu .icon-shape .label rect,#mermaid-svg-43PcZyr4EgTm7ueu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-43PcZyr4EgTm7ueu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-43PcZyr4EgTm7ueu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-43PcZyr4EgTm7ueu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Unacked 消息集合
deliveryTag=5
deliveryTag=6
deliveryTag=7
deliveryTag=8
basicAck(8, true)
确认 <= 8 的所有未确认消息
5, 6, 7, 8 全部删除
basicAck(8, false)
只确认 deliveryTag=8
5, 6, 7 仍然 Unacked
注意:
- 批量确认能减少网络开销;
- 但如果前面的消息实际还没处理完,会造成误删;
- 因此只适合严格顺序处理且确认边界明确的场景。
3. basicReject:拒绝单条消息
channel.basicReject(deliveryTag, requeue);
含义:
告诉 RabbitMQ:这条消息我不处理了,你决定重新入队还是丢弃 / 死信。
| 参数 | 类型 | 含义 |
|---|---|---|
deliveryTag |
long |
要拒绝的投递编号 |
requeue |
boolean |
是否重新入队 |
requeue=true
channel.basicReject(deliveryTag, true);
消息重新进入队列,等待再次投递。
Unacked -> Ready -> Consumer
适合:
- 临时性异常;
- 数据库短暂不可用;
- 下游服务短暂超时。
风险:
如果错误是永久性的,例如消息格式错误,
requeue=true会制造无限重试。
requeue=false
channel.basicReject(deliveryTag, false);
消息不会重新入队。
| 是否配置 DLX | 结果 |
|---|---|
| 配置了死信交换机 | 进入死信队列 |
| 没配置死信交换机 | 被 RabbitMQ 丢弃 |
4. basicNack:增强版拒绝,支持批量
channel.basicNack(deliveryTag, multiple, requeue);
basicNack 是 RabbitMQ 对 AMQP 0-9-1 的扩展,用来弥补 basicReject 不能批量拒绝的问题。
| 参数 | 类型 | 含义 |
|---|---|---|
deliveryTag |
long |
拒绝的投递编号 |
multiple |
boolean |
是否批量拒绝 |
requeue |
boolean |
是否重新入队 |
示例:
channel.basicNack(deliveryTag, false, true);
表示:
- 拒绝当前消息;
- 重新放回队列;
- 后续可能再次投递。
批量拒绝:
channel.basicNack(8, true, false);
含义:
Unacked tags: 5, 6, 7, 8
basicNack(8, true, false)
Result:
5, 6, 7, 8 全部拒绝
如果配置 DLX -> 进入死信队列
否则 -> 丢弃
图表占位:
#mermaid-svg-d33Fv5F6gk1feLj0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d33Fv5F6gk1feLj0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d33Fv5F6gk1feLj0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d33Fv5F6gk1feLj0 .error-icon{fill:#552222;}#mermaid-svg-d33Fv5F6gk1feLj0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d33Fv5F6gk1feLj0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d33Fv5F6gk1feLj0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d33Fv5F6gk1feLj0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d33Fv5F6gk1feLj0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d33Fv5F6gk1feLj0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d33Fv5F6gk1feLj0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d33Fv5F6gk1feLj0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d33Fv5F6gk1feLj0 .marker.cross{stroke:#333333;}#mermaid-svg-d33Fv5F6gk1feLj0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d33Fv5F6gk1feLj0 p{margin:0;}#mermaid-svg-d33Fv5F6gk1feLj0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d33Fv5F6gk1feLj0 .cluster-label text{fill:#333;}#mermaid-svg-d33Fv5F6gk1feLj0 .cluster-label span{color:#333;}#mermaid-svg-d33Fv5F6gk1feLj0 .cluster-label span p{background-color:transparent;}#mermaid-svg-d33Fv5F6gk1feLj0 .label text,#mermaid-svg-d33Fv5F6gk1feLj0 span{fill:#333;color:#333;}#mermaid-svg-d33Fv5F6gk1feLj0 .node rect,#mermaid-svg-d33Fv5F6gk1feLj0 .node circle,#mermaid-svg-d33Fv5F6gk1feLj0 .node ellipse,#mermaid-svg-d33Fv5F6gk1feLj0 .node polygon,#mermaid-svg-d33Fv5F6gk1feLj0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d33Fv5F6gk1feLj0 .rough-node .label text,#mermaid-svg-d33Fv5F6gk1feLj0 .node .label text,#mermaid-svg-d33Fv5F6gk1feLj0 .image-shape .label,#mermaid-svg-d33Fv5F6gk1feLj0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-d33Fv5F6gk1feLj0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d33Fv5F6gk1feLj0 .rough-node .label,#mermaid-svg-d33Fv5F6gk1feLj0 .node .label,#mermaid-svg-d33Fv5F6gk1feLj0 .image-shape .label,#mermaid-svg-d33Fv5F6gk1feLj0 .icon-shape .label{text-align:center;}#mermaid-svg-d33Fv5F6gk1feLj0 .node.clickable{cursor:pointer;}#mermaid-svg-d33Fv5F6gk1feLj0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d33Fv5F6gk1feLj0 .arrowheadPath{fill:#333333;}#mermaid-svg-d33Fv5F6gk1feLj0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d33Fv5F6gk1feLj0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d33Fv5F6gk1feLj0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d33Fv5F6gk1feLj0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d33Fv5F6gk1feLj0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d33Fv5F6gk1feLj0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d33Fv5F6gk1feLj0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d33Fv5F6gk1feLj0 .cluster text{fill:#333;}#mermaid-svg-d33Fv5F6gk1feLj0 .cluster span{color:#333;}#mermaid-svg-d33Fv5F6gk1feLj0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d33Fv5F6gk1feLj0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d33Fv5F6gk1feLj0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-d33Fv5F6gk1feLj0 .icon-shape,#mermaid-svg-d33Fv5F6gk1feLj0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d33Fv5F6gk1feLj0 .icon-shape p,#mermaid-svg-d33Fv5F6gk1feLj0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d33Fv5F6gk1feLj0 .icon-shape .label rect,#mermaid-svg-d33Fv5F6gk1feLj0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d33Fv5F6gk1feLj0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d33Fv5F6gk1feLj0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d33Fv5F6gk1feLj0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} true
false
是
否
false
true
true
false
basicReject(tag, requeue)
requeue?
重新入队 Ready
是否配置死信交换机 DLX?
进入死信队列
直接丢弃
basicNack(tag, multiple, requeue)
multiple?
只拒绝当前 tag
拒绝 <= tag 的所有未确认消息
requeue?
四、basicAck、basicReject、basicNack 对比总表
| 方法 | 正 / 负确认 | 是否支持批量 | 是否支持重新入队 | 典型用途 |
|---|---|---|---|---|
basicAck |
正确认 | 是 | 否 | 业务处理成功,删除消息 |
basicReject |
负确认 | 否 | 是 | 拒绝单条消息 |
basicNack |
负确认 | 是 | 是 | 批量拒绝、批量重回队列、批量死信 |
参数组合速查:
| API | 参数组合 | 效果 |
|---|---|---|
basicAck(tag, false) |
单条 ack | 删除当前消息 |
basicAck(tag, true) |
批量 ack | 删除当前及之前未确认消息 |
basicReject(tag, true) |
单条拒绝并重回队列 | 可能再次消费 |
basicReject(tag, false) |
单条拒绝不重回 | 进入 DLX 或丢弃 |
basicNack(tag, false, true) |
单条 nack 并重回 | 适合临时失败 |
basicNack(tag, true, false) |
批量 nack 不重回 | 批量进入 DLX 或丢弃 |
五、Spring Boot 整合策略
Spring AMQP 提供了三种确认模式:
java
public enum AcknowledgeMode {
NONE,
MANUAL,
AUTO
}
配置示例:
yml
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
prefetch: 10
1. NONE
等价于 RabbitMQ 的自动确认。
| 行为 | 说明 |
|---|---|
| 是否发送 ack | 不发送 |
| Broker 视角 | 消息投递后立即成功 |
| 异常后是否重试 | 不会 |
| 风险 | 消费失败也可能丢消息 |
适合非核心链路。
2. AUTO
Spring AMQP 默认模式。
| 情况 | Spring 容器行为 |
|---|---|
| Listener 正常返回 | 自动 ack |
| Listener 抛异常 | 不正常 ack,交给容器异常策略处理 |
它比 NONE 安全,但要注意:
- 异常处理策略会影响是否 requeue;
- 可能出现失败消息反复投递;
- 必须结合重试、死信、异常分类一起设计。
3. MANUAL
生产环境最可控的方式。
java
@RabbitListener(queues = "order.queue")
public void onMessage(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
String body = new String(message.getBody(), StandardCharsets.UTF_8);
// 1. 参数校验
// 2. 幂等判断
// 3. 执行业务事务
// 4. 提交成功后 ack
channel.basicAck(deliveryTag, false);
} catch (BizNonRetryableException e) {
// 不可重试异常:进入死信队列或丢弃
channel.basicNack(deliveryTag, false, false);
} catch (Exception e) {
// 临时异常:可以重回队列,但要防止无限重试
channel.basicNack(deliveryTag, false, true);
}
}
推荐策略:
| 异常类型 | 示例 | 建议处理 |
|---|---|---|
| 参数错误 | JSON 格式不对、字段缺失 | basicNack(false, false) 进死信 |
| 幂等冲突 | 消息已处理 | basicAck |
| 临时异常 | DB 超时、RPC 超时 | 有限制地 requeue 或延迟重试 |
| 永久异常 | 业务状态非法 | 进入死信队列人工排查 |
图表占位:
#mermaid-svg-cLKW54BdOS5kaSe8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cLKW54BdOS5kaSe8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cLKW54BdOS5kaSe8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cLKW54BdOS5kaSe8 .error-icon{fill:#552222;}#mermaid-svg-cLKW54BdOS5kaSe8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cLKW54BdOS5kaSe8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cLKW54BdOS5kaSe8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cLKW54BdOS5kaSe8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cLKW54BdOS5kaSe8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cLKW54BdOS5kaSe8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cLKW54BdOS5kaSe8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cLKW54BdOS5kaSe8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cLKW54BdOS5kaSe8 .marker.cross{stroke:#333333;}#mermaid-svg-cLKW54BdOS5kaSe8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cLKW54BdOS5kaSe8 p{margin:0;}#mermaid-svg-cLKW54BdOS5kaSe8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cLKW54BdOS5kaSe8 .cluster-label text{fill:#333;}#mermaid-svg-cLKW54BdOS5kaSe8 .cluster-label span{color:#333;}#mermaid-svg-cLKW54BdOS5kaSe8 .cluster-label span p{background-color:transparent;}#mermaid-svg-cLKW54BdOS5kaSe8 .label text,#mermaid-svg-cLKW54BdOS5kaSe8 span{fill:#333;color:#333;}#mermaid-svg-cLKW54BdOS5kaSe8 .node rect,#mermaid-svg-cLKW54BdOS5kaSe8 .node circle,#mermaid-svg-cLKW54BdOS5kaSe8 .node ellipse,#mermaid-svg-cLKW54BdOS5kaSe8 .node polygon,#mermaid-svg-cLKW54BdOS5kaSe8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cLKW54BdOS5kaSe8 .rough-node .label text,#mermaid-svg-cLKW54BdOS5kaSe8 .node .label text,#mermaid-svg-cLKW54BdOS5kaSe8 .image-shape .label,#mermaid-svg-cLKW54BdOS5kaSe8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-cLKW54BdOS5kaSe8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cLKW54BdOS5kaSe8 .rough-node .label,#mermaid-svg-cLKW54BdOS5kaSe8 .node .label,#mermaid-svg-cLKW54BdOS5kaSe8 .image-shape .label,#mermaid-svg-cLKW54BdOS5kaSe8 .icon-shape .label{text-align:center;}#mermaid-svg-cLKW54BdOS5kaSe8 .node.clickable{cursor:pointer;}#mermaid-svg-cLKW54BdOS5kaSe8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cLKW54BdOS5kaSe8 .arrowheadPath{fill:#333333;}#mermaid-svg-cLKW54BdOS5kaSe8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cLKW54BdOS5kaSe8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cLKW54BdOS5kaSe8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cLKW54BdOS5kaSe8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cLKW54BdOS5kaSe8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cLKW54BdOS5kaSe8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cLKW54BdOS5kaSe8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cLKW54BdOS5kaSe8 .cluster text{fill:#333;}#mermaid-svg-cLKW54BdOS5kaSe8 .cluster span{color:#333;}#mermaid-svg-cLKW54BdOS5kaSe8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cLKW54BdOS5kaSe8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cLKW54BdOS5kaSe8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-cLKW54BdOS5kaSe8 .icon-shape,#mermaid-svg-cLKW54BdOS5kaSe8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cLKW54BdOS5kaSe8 .icon-shape p,#mermaid-svg-cLKW54BdOS5kaSe8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cLKW54BdOS5kaSe8 .icon-shape .label rect,#mermaid-svg-cLKW54BdOS5kaSe8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cLKW54BdOS5kaSe8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cLKW54BdOS5kaSe8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cLKW54BdOS5kaSe8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 处理成功
可重试异常
不可重试异常
是
否
Spring Boot @RabbitListener 接收消息
获取 deliveryTag
执行业务逻辑
channel.basicAck(deliveryTag, false)
RabbitMQ 删除消息
channel.basicNack(deliveryTag, false, true)
消息重新入队 Ready
channel.basicNack(deliveryTag, false, false)
是否配置 DLX?
进入死信队列
消息丢弃
六、常见坑点分析
坑 1:业务没处理完就 ack
错误写法:
java
channel.basicAck(deliveryTag, false);
doBusiness();
如果 doBusiness() 抛异常,消息已经被 RabbitMQ 删除。
正确顺序:
java
doBusiness();
channel.basicAck(deliveryTag, false);
坑 2:无限 requeue=true
java
channel.basicNack(deliveryTag, false, true);
如果消息本身就是坏数据,会出现:
消费 -> 异常 -> requeue -> 再消费 -> 再异常
结果:
- CPU 飙升;
- 日志刷屏;
- 队列吞吐被坏消息拖垮;
- 正常消息被阻塞。
好似
建议:
- 增加重试次数;
- 超过阈值后进入死信队列;
- 对不可恢复异常直接
requeue=false。
坑 3:multiple=true 用错导致误确认
channel.basicAck(deliveryTag, true);
如果前面的消息还没完成业务处理,就会被一起确认删除。
建议:
| 场景 | multiple 建议 |
|---|---|
| 单线程顺序消费 | 可谨慎使用 true |
| 并发处理 | 尽量使用 false |
| 每条消息独立事务 | 使用 false |
| 批处理且边界明确 | 可使用 true |
坑 4:忽略 prefetch 导致 Unacked 暴涨
prefetch 决定一个消费者最多可以同时持有多少未确认消息。
spring:
rabbitmq:
listener:
simple:
prefetch: 10
| prefetch | 效果 |
|---|---|
| 1 | 严格一条条处理,吞吐较低,顺序性较好 |
| 10 / 50 | 常见生产配置,吞吐与风险平衡 |
| 250 | Spring AMQP 较新的默认值,需结合业务处理能力评估 |
| 0 | 无限制,风险很高 |
Unacked可以理解为消费者手里的"在途消息窗口"。窗口越大,吞吐可能越高,但故障恢复和内存压力也越大。
七、生产建议清单
| 建议 | 说明 |
|---|---|
核心业务使用 MANUAL |
确认边界由业务控制 |
| ack 放在事务成功之后 | 避免消息已删但业务失败 |
| 消费逻辑必须幂等 | RabbitMQ 保证至少一次投递,不保证只投一次 |
| 区分可重试和不可重试异常 | 不要所有异常都 requeue |
| 配置 DLX | 给失败消息留出口 |
| 设置合理 prefetch | 控制 Unacked 窗口 |
| 监控 Ready / Unacked | 这是排查消费问题的一线指标 |
| 避免跨 Channel ack | deliveryTag 只在当前 Channel 有效 |
总结
RabbitMQ 的消费端确认机制,本质上是在回答一个问题:
Broker 什么时候可以安全地删除一条消息?
答案取决于确认模式:
autoAck=true:投递出去就删除,快但不可靠;autoAck=false:消费者明确确认后删除,可靠但需要你设计好失败路径;basicAck:成功,删除;basicReject:单条拒绝,可选择重回队列;basicNack:增强拒绝,支持批量和重回队列;Ready:还没投递;Unacked:已投递但未确认。
真正成熟的 RabbitMQ 消费端,不是简单写一个 @RabbitListener,而是把 确认时机、异常分类、幂等控制、重试策略、死信队列、prefetch 窗口和监控指标放在一起设计。
这才是消息系统从「能跑」走向「可靠」的关键一步。