文章目录
- [一、为什么 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。
工作流程
- Broker 将消息投递给 Consumer
- Consumer 执行业务逻辑
- Consumer 发送 ACK
- 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 实现可靠消息的核心机制。