[RabbitMQ高级特性] 消息确认机制:从 Ready / Unacked 到 basicAck、basicReject、basicNack 的底层拆解

前言

在 RabbitMQ 中,消息投递成功 不等于业务处理成功

生产者把消息投递到 RabbitMQ,只解决了「消息进入 Broker」的问题;RabbitMQ 把消息推给消费者,也只解决了「消息到达消费者进程」的问题。真正困难的是:

消费者拿到消息后,业务逻辑执行到一半宕机怎么办?

消费者抛异常了,消息是丢弃、重试,还是进入死信队列?

控制台里的 ReadyUnacked 到底在表达什么?

这就是 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 消息都被消费者拿走了,但还没处理完 消费者可能卡住或确认逻辑异常
ReadyUnacked 都高 既有堆积,也有大量处理中消息 消费能力不足,需扩容或限流

一个非常典型的故障现场

复制代码
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。

业务去重请使用业务唯一键,例如 orderNoeventIdmessageId


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 窗口和监控指标放在一起设计。

这才是消息系统从「能跑」走向「可靠」的关键一步。

相关推荐
枫华落尽2 小时前
【Hadoop01-完全分布式运行模式】
分布式
隔壁阿布都2 小时前
ShedLock 分布式定时任务锁框架介绍
spring boot·分布式
文艺倾年2 小时前
【强化学习】数学推导专题,20W字总结(十五)
人工智能·分布式·大模型·强化学习·vibecoding
guslegend2 小时前
第1章:初始Kafka
分布式·kafka
ACP广源盛1392462567320 小时前
GSV5600@ACP#多接口协议转换芯片,物理 AI 便携终端的互联核心
大数据·人工智能·分布式·嵌入式硬件·spark
极客先躯1 天前
高级java每日一道面试题-2026年02月12日-实战篇[Docker]-什么是容器的 Seccomp 配置?如何自定义?
java·运维·分布式·docker·容器·自动化·文件
Francek Chen1 天前
【大数据处理与分析】MapReduce:06 MapReduce编程实践
大数据·hadoop·分布式·mapreduce
小马爱打代码1 天前
Kafka消息队列监控:Topic积压、吞吐量、Broker负载及消费者组全观测
分布式·kafka
半夜修仙1 天前
延迟队列的介绍及常见问题
java·数据库·中间件·rabbitmq