深入理解:RabbitMQ 中的 ACK / NACK 有什么区别?

文章目录

  • [一、为什么 RabbitMQ 需要 ACK / NACK?](#一、为什么 RabbitMQ 需要 ACK / NACK?)
  • [二、ACK 是什么?](#二、ACK 是什么?)
  • [三、NACK 是什么?](#三、NACK 是什么?)
  • [四、NACK 之后消息会发生什么?](#四、NACK 之后消息会发生什么?)
  • [五、ACK vs NACK 对比总结](#五、ACK vs NACK 对比总结)
  • [六、ACK / NACK 与自动确认(autoAck)](#六、ACK / NACK 与自动确认(autoAck))
  • [七、ACK / NACK 与"至少一次投递"](#七、ACK / NACK 与“至少一次投递”)
  • 八、总结

在使用 RabbitMQ 的过程中, ACK / NACK 是保证消息可靠性最核心的机制之一

很多人知道"ACK 是确认、NACK 是拒绝",但如果继续追问:

  • ACK / NACK 到底解决了什么问题?
  • NACK 之后消息一定会重试吗?
  • 和自动确认(autoAck)有什么本质区别?
  • 为什么 RabbitMQ 只能做到"至少一次投递"?

一、为什么 RabbitMQ 需要 ACK / NACK?

RabbitMQ 是一个 异步消息系统,生产者发送消息后,并不知道消费者什么时候、是否能成功处理消息。

如果没有确认机制,会出现严重问题:

  • 消费者刚拿到消息就宕机
  • 消息已经从队列移除
  • 业务还没执行完

👉 消息直接丢失

因此,AMQP 协议引入了 消息确认机制(Acknowledgement),让消费者明确告诉 Broker:

这条消息,我处理得怎么样了。


二、ACK 是什么?

ACK(Acknowledge)

ACK 表示:消息已经被成功消费

当消费者处理完一条消息,并确认没有任何问题时,会向 Broker 发送 ACK。

工作流程

  1. Broker 将消息投递给 Consumer
  2. Consumer 执行业务逻辑
  3. Consumer 发送 ACK
  4. Broker 永久删除这条消息

Java 示例

java 复制代码
channel.basicAck(deliveryTag, false);

参数说明:

  • deliveryTag:消息唯一标识
  • false:只确认当前这一条(不是批量)

📌 ACK 的本质:

告诉 RabbitMQ:

"这条消息我已经处理完成了,你可以删掉。"


三、NACK 是什么?

NACK(Negative Acknowledge)

NACK 表示:消息消费失败

当消费者在处理消息时出现异常,例如:

  • 数据库不可用
  • 下游接口超时
  • 业务校验失败

就应该使用 NACK。

Java 示例

java 复制代码
channel.basicNack(deliveryTag, false, true);

参数说明:

参数 含义
deliveryTag 消息标识
multiple 是否批量
requeue 是否重新入队

四、NACK 之后消息会发生什么?

这是 ACK / NACK 最核心、也最容易被问到的点

requeue = true(重新入队)

java 复制代码
channel.basicNack(tag, false, true);

结果:

  • 消息重新回到队列
  • 等待下一次投递
  • 可能被同一个 Consumer 再次消费

⚠️ 风险:

  • 如果逻辑一直失败,会 无限重试
  • 可能造成 CPU 飙高、队列阻塞

requeue = false(不重新入队)

java 复制代码
channel.basicNack(tag, false, false);

结果:

  • 消息被丢弃
  • 如果队列配置了 死信队列(DLQ)

👉 消息进入死信队列

📌 生产环境常用策略:

场景 做法
临时异常 NACK + requeue
业务错误 NACK + DLQ
数据非法 直接进入死信队列

五、ACK vs NACK 对比总结

维度 ACK NACK
含义 消费成功 消费失败
消息结果 立即删除 重试 / 丢弃 / DLQ
是否可靠
是否可能重复
使用时机 业务成功 业务异常

六、ACK / NACK 与自动确认(autoAck)

autoAck = true(不推荐)

java 复制代码
channel.basicConsume(queue, true, consumer);

特点:

  • 消息一投递就算"成功"
  • Consumer 宕机也不会重发
  • 存在消息丢失风险

手动 ACK(推荐)

java 复制代码
channel.basicConsume(queue, false, consumer);

特点:

  • 业务成功 → ACK
  • 业务失败 → NACK
  • 保证至少一次投递

📌 结论:

生产环境一定使用手动 ACK


七、ACK / NACK 与"至少一次投递"

RabbitMQ 的投递语义是:

At-Least-Once(至少一次)

原因:

  • Consumer 在 ACK 之前宕机
  • Broker 不知道消息是否处理完成
  • 消息会被重新投递

因此:

  • 消息可能重复
  • 消息不会丢失

👉 幂等性必须由业务层保证。

就像快递必须等你签收才算完成,没签收前你要是出事了快递员就会再送一次,所以快递可能送两次,但绝不会不送,重复要你自己处理。


八、总结

ACK 能保证消息不重复吗?

不能。

RabbitMQ 只能保证至少一次投递,

幂等性需要业务侧保证。


basicReject 和 basicNack 有什么区别?

方法 说明
basicReject 只能拒绝一条消息
basicNack 支持批量,更灵活

👉 实际开发中 优先使用 basicNack


什么情况下会用 NACK + requeue?

  • 数据库短暂不可用
  • 网络抖动
  • 下游系统瞬时故障

ACK 表示消息已成功消费并可删除;
NACK 表示消费失败,消息可重试或进入死信队列。
手动 ACK + 合理 NACK 是 RabbitMQ 实现可靠消息的核心机制。

相关推荐
用户8307196840821 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者2 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者4 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧5 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖5 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农5 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者5 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀5 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Ronin3055 天前
信道管理模块和异步线程模块
开发语言·c++·rabbitmq·异步线程·信道管理
Asher05095 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式