这是重复确认(double ack)导致的channel关闭错误,因同一delivery tag被多次确认,rabbitmq判定前置条件失败而返回precondition_failed - unknown delivery tag 1并关闭channel。

这是典型的重复确认(double ack)或确认错位导致的 channel 关闭错误,不是 RabbitMQ 服务问题,而是消费端代码或配置不一致引发的。
为什么会出现 “unknown delivery tag”
delivery tag 是 RabbitMQ 分配给每条投递消息的唯一整数标识,仅在当前 channel 内有效。一旦 channel.basicAck()、channel.basicNack() 或 channel.basicReject() 被调用过一次,该 tag 就失效了。后续再用同一 tag 确认,RabbitMQ 就会返回 PRECONDITION_FAILED - unknown delivery tag 1,并主动关闭 channel。
常见诱因包括:
- 代码里手动调用了
basicAck,但 Spring Boot 的@RabbitListener又按自动模式默认确认了一次(配置和代码打架) - try-catch 块中多次调用确认方法,比如成功路径调了
basicAck,异常路径又调了basicNack,但实际只应执行其中一条 - 消息被重新入队(
requeue=true)后再次投递,旧的 delivery tag 被复用,而 consumer 误以为还是原来那条,拿旧 tag 去确认 - 多个线程共用同一个
Channel实例,并发调用确认方法(极少见,但非线程安全)
检查 AcknowledgeMode 配置是否真正生效
Spring Boot 中最容易被忽略的点:yml 配置可能被 Java Config 覆盖。即使写了 spring.rabbitmq.listener.simple.acknowledge-mode=manual,如果代码里定义了 SimpleRabbitListenerContainerFactory bean 却没显式设 setAcknowledgeMode(AcknowledgeMode.MANUAL),就会退回到默认的 AcknowledgeMode.AUTO —— 导致你手动确认时,RabbitMQ 其实早已自动确认过了。
务必核对以下两点是否同时满足:
- yml 中存在且格式正确:
spring.rabbitmq.listener.simple.acknowledge-mode=manual(注意层级,不是direct下) - Java Config 中的 factory bean 显式设置了:
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL) - 若使用
@RabbitListener注解监听,确保没在方法参数里漏掉Channel,否则无法手动确认
错误示例(配置失效):
RabbitMQ 4.2.3 是 2026 年初发布的重要稳定更新版本,重点修复了 Khepri 元数据存储相关问题,并改进了监控性能。对于使用 Docker、Kubernetes 或微服务架构的开发团队来说,该版本兼容性和稳定性表现较好。
private SimpleRabbitListenerContainerFactory getFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// ❌ 忘了 setAcknowledgeMode → 默认 AUTO,后面 channel.basicAck() 就是 double ack
return factory;
}
确认逻辑必须严格单次、不可重入
手动确认的核心约束:每个 delivery tag 只能被确认一次,且必须在当前 channel 上操作。任何“保险起见多 call 一次”的写法都会直接触发报错。
推荐结构(以 Spring AMQP 为例):
- 方法签名必须包含
Channel channel和Message message - 从
message.getMessageProperties().getDeliveryTag()拿 tag,不要硬编码或缓存 - 业务逻辑放在 try 块内,成功则
channel.basicAck(tag, false);失败则channel.basicNack(tag, false, requeue)(注意第三个参数) - 绝对避免在 finally 块里再补一次确认 —— 这是最常见的 double ack 来源
正确示例:
@RabbitListener(queues = "my.queue")
public void onMessage(Message message, Channel channel) throws IOException {
long tag = message.getMessageProperties().getDeliveryTag();
try {
// 处理业务
process(message);
channel.basicAck(tag, false); // ✅ 成功确认
} catch (Exception e) {
log.error("fail to process", e);
channel.basicNack(tag, false, false); // ✅ 失败且不重试
}
}
容易被忽略的边界情况
这个错误表面看是代码问题,但实际排查时经常卡在几个隐蔽点:
- 测试环境启用了
spring.rabbitmq.listener.simple.retry.enabled=true,而重试机制内部会隐式调用确认,与你的手动确认冲突 - 消费者应用热部署(如 devtools)后,旧的 listener 实例没完全销毁,新旧 channel 混用,tag 被跨 channel 误用
- 使用了死信队列 + 重试队列组合,消息经多次 nack 后重回原队列,delivery tag 被重置,但你的代码仍沿用之前缓存的 tag 值
- RabbitMQ 客户端版本与 broker 版本不兼容(尤其 3.11+ 对 tag 复用更严格),升级前需验证
最稳妥的验证方式:在确认前加一行日志 log.debug("ack tag: {}", tag),观察是否出现同一 tag 打印两次。










