深入理解: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 实现可靠消息的核心机制。

相关推荐
七夜zippoe2 小时前
RabbitMQ与Celery深度集成:构建高性能Python异步任务系统
分布式·python·rabbitmq·celery·amqp
小雪_Snow2 小时前
RabbitMQ 安装教程【docker】
rabbitmq
sunnyday04262 小时前
深入理解分布式锁:基于Redisson的多样化锁实现
spring boot·redis·分布式
熏鱼的小迷弟Liu2 小时前
【消息队列】如何在RabbitMQ中处理消息的重复消费问题?
面试·消息队列·rabbitmq
txinyu的博客11 小时前
解析业务层的key冲突问题
开发语言·c++·分布式
你好龙卷风!!!21 小时前
rabbitMQ入门 (mac)
macos·rabbitmq·ruby
yumgpkpm21 小时前
Cloudera CDH、CDP、Hadoop大数据+决策模型及其案例
大数据·hive·hadoop·分布式·spark·kafka·cloudera
IT大白1 天前
4、Kafka原理-Consumer
分布式·kafka