ACK 是英文单词 Acknowledgement(确认、致谢)的缩写。
在计算机网络和 RabbitMQ 这样的消息队列中,它的核心含义就是:"收到且确认"。
为什么手动 ACK 能防止消息丢失? 带着这个问题继续阅读
我们需要对比一下自动 ACK 的风险,并结合"快递员送货"的例子来理解。
1. 核心区别:消息什么时候被删除?
RabbitMQ 作为一个消息中间件,它的主要任务是把消息从 A 传给 B。但是,它需要知道什么时候可以把原本存在内存或磁盘里的这条消息删掉。
❌ 自动 ACK (Auto Acknowledge) ------ "扔在门口就跑"
如果设置 noAck: true (自动 ACK),RabbitMQ 的逻辑是这样的:
-
RabbitMQ 把消息发送给消费者(你的代码)。
-
只要消息发出了,RabbitMQ 瞬间就认为"任务完成",并立即从内存/磁盘中删除这条消息。
-
风险点: 此时你的代码刚收到消息,还没开始处理(或者处理到一半)。如果这个时候:
-
你的机器断电了。
-
你的 Node.js 进程崩溃了 (Exception)。
-
数据库连接断开导致报错。
-
结果: 你的代码挂了,没处理完业务;而 RabbitMQ 那边已经把消息删了。这条消息就彻底消失了,无法找回。
✅ 手动 ACK (Manual Acknowledge) ------ "签收才算完"
如果设置 noAck: false (手动 ACK),流程就变了:
-
RabbitMQ 把消息发送给消费者。
-
RabbitMQ 不会删除消息 ,而是将这条消息标记为
Unacked(未确认) 状态。 -
消费者开始运行业务逻辑(查数据库、计算、调接口...)。
-
关键时刻:
-
如果成功: 你的代码显式调用
channel.ack(msg)。RabbitMQ 收到信号,这才把消息删除。 -
如果崩溃(未发送 ACK): 如果你的消费者在调用
ack()之前断开了连接(比如进程挂了),RabbitMQ 会发现:"哎,这个消费者断连了,但它手里还有条消息没确认!"
-
结果: RabbitMQ 会自动把这条消息重新放回队列头部 (Re-queue),发给下一个可用的消费者(或者是重启后的同一个消费者)。
2. 它是如何解决丢失问题的?(情景模拟)
让我们看一个具体的**"崩溃恢复"**场景:
-
场景: 这是一个订单处理系统。
-
动作: 收到消息
{ "orderId": 100, "action": "deduct_stock" }。
如果没有手动 ACK(消息丢失):
RabbitMQ 发送消息 -> RabbitMQ 删除消息 -> 消费者收到 -> 开始扣减库存 -> 代码报错:数据库连接超时,程序崩溃。
结局: 库存没扣减,消息也没了。这就是严重的数据不一致。
如果有手动 ACK(消息找回):
RabbitMQ 发送消息 -> 标记为 Unacked -> 消费者收到 -> 开始扣减库存 -> 代码报错:数据库连接超时,程序崩溃 (注意:此时没有执行
ack())。RabbitMQ 监测到 TCP 连接断开 -> 将消息状态从 Unacked 改回 Ready -> 重新投递给备用的消费者 B。
消费者 B 收到消息 -> 扣减库存成功 -> 发送 ACK -> RabbitMQ 删除消息。
结局: 业务最终被成功处理。
3. 关于 ACK 与 NACK 的区别
既然是为了防止丢失,为什么还需要 NACK (Negative Acknowledgement)?
-
ACK (BasicAck): 告诉 RabbitMQ:"我处理成功了,你可以删了。"
-
NACK (BasicNack) / Reject: 告诉 RabbitMQ:"我处理不了这条消息。"
在手动模式下,显式调用 NACK 通常用于以下两种情况:
-
处理失败,需要重试 (requeue: true):
- 比如"远程接口暂时超时"。你调用
nack(msg, false, true)。RabbitMQ 会把消息放回队列,稍后再次尝试投递。
- 比如"远程接口暂时超时"。你调用
-
无法处理,直接丢弃 (requeue: false):
- 比如"消息格式错误"或"恶意数据"。你调用
nack(msg, false, false)。RabbitMQ 会直接删除它(或者转发到死信队列 Dead Letter Exchange),防止这条坏消息无限循环卡死消费者。
- 比如"消息格式错误"或"恶意数据"。你调用
总结
手动 ACK 机制之所以能解决消息丢失,是因为它将"消息删除的控制权"从 RabbitMQ 转移到了你的业务代码手中。
-
只要你没说"OK" (
ack),RabbitMQ 就永远不敢删这条消息。 -
只要连接断开时你没说"OK",RabbitMQ 就默认你"挂了",并帮你把消息救回来重发。
这就是所谓的 At-least-once(至少一次投递) 保证。