必须在application.yml中配置acknowledge-mode: manual、prefetch: 1、concurrency与max-concurrency为1,且消费者方法签名需含channel和message参数,才能正确使用basicack/basicnack/basicreject。

application.yml里必须配对的三个关键项
手动ACK不是加个注解就生效的,Spring Boot默认是autoAck=true,必须在配置文件中显式关闭自动确认,并指定监听器类型。漏掉任意一项都会导致basicAck调用无效,消息仍被自动删除。
-
spring.rabbitmq.listener.simple.acknowledge-mode: manual—— 这是核心开关,告诉Spring使用AcknowledgeMode.MANUAL -
spring.rabbitmq.listener.simple.prefetch: 1—— 建议设为1,避免单个消费者堆积多条Unacked消息,影响并发吞吐和重试逻辑 -
spring.rabbitmq.listener.simple.concurrency: 1和spring.rabbitmq.listener.simple.max-concurrency: 1—— 初期调试时建议锁死单线程,避免多线程竞争deliveryTag引发IOException: channel is closed
Consumer方法里必须传入Channel和Message参数
Spring Boot的@RabbitListener方法若想手动ACK,签名必须包含Channel和Message(或org.springframework.amqp.core.Message),否则拿不到deliveryTag,调用basicAck会抛NullPointerException。
- 错误写法:
public void handle(String body)—— 没有Channel,无法确认 - 正确写法:
public void handle(String body, Channel channel, Message message) throws IOException -
message.getMessageProperties().getDeliveryTag()是唯一合法的deliveryTag来源,不能硬编码或自增
basicAck、basicNack、basicReject三者怎么选
它们不是功能等价的替代品,语义完全不同,选错会导致消息丢失或无限循环重投。
搜索 Hivemind 精选的 Web3 营销知识库(RAG),获取从业者洞察、框架、实操指南和案例研究。当:(1) an... 时使用此技能。
-
channel.basicAck(tag, false):仅用于**确定成功**场景,比如数据库已提交、下游API返回200。调用后消息立即从队列移除 -
channel.basicNack(tag, false, true):用于**临时失败需重试**,如网络超时、第三方服务不可用。第三个参数true表示重新入队,但注意:它可能被立刻再次投递给同一消费者,造成死循环,需配合x-dead-letter-exchange做兜底 -
channel.basicReject(tag, false):用于**永久失败且不重试**,比如消息格式非法、业务主键冲突。第二个参数false表示只拒绝当前消息,不批量处理
特别注意:basicNack(tag, false, false) 和 basicReject(tag, false) 效果相同——消息被丢弃(进dead-letter-queue或直接丢弃),但前者在RabbitMQ 3.8+中更推荐,因支持批量拒绝
为什么消息卡在Unacked状态不动了
这是手动ACK最常踩的坑:没调用确认方法,或调用后抛了未捕获异常,导致Channel连接意外关闭。RabbitMQ不会主动回收Unacked消息,它只看TCP连接是否存活。
- 检查日志里是否有
java.io.IOException: connection is closed或shutdown signal - 确保
try-catch包裹全部业务逻辑,且catch块里至少调用一次basicNack(..., false),不能让异常穿透出方法体 - Web管理界面的
Unacked数持续上涨,说明消费者进程活着但没发ACK,大概率是业务代码卡死、死锁或异步线程未等待完成
真正难处理的从来不是“怎么写ACK”,而是“什么时机写”和“写完之后断连了怎么办”——这两点决定了消息是可靠落地,还是静默消失。










