RabbitMQ 消息确认机制
RabbitMQ 采用"投递—确认"模型保证消息不丢:消息发给消费者后 broker 并不会立刻删除它,而是等消费者返回确认,确认成功才真正移除。理解这套机制是写出可靠消息应用的前提。
消息何时被删除
区别只在"确认"这一步:自动确认下,broker 把消息发出就算完成并删除;手动确认下,消费者处理完调用 ack 后 broker 才删除。若消费者在处理中途断开,未确认的消息会被重新入队,稍后再投给其它消费者。
三种确认方式
| 方式 | 触发 | broker 行为 | 适用 |
|---|---|---|---|
| 自动确认 | 无需消费者参与,投递即算完成 | 立即删除 | 允许少量丢失 |
| 手动确认 basicAck | 处理成功后调用 | 收到 ack 才删除 | 需要可靠处理 |
| 否定确认 basicNack/basicReject | 处理失败后调用 | 重回队列或丢弃/转死信 | 失败重试 |
各客户端默认确认模式并不一致,务必按文档确认;需要手动确认时要显式开启(pika 消费时传 auto_ack=False,Spring 将确认模式设为 MANUAL)。
ack 与 requeue 语义
basicAck 只表示"这条我处理完了"。处理失败时用 basicReject 或 basicNack 告诉 broker:
- requeue=true:消息重回队列,稍后重新投递(可能是本消费者,也可能换一个);
- requeue=false:消息不回队列,直接被丢弃;若业务队列配置了死信交换机,则转入死信队列(见死信队列章节)。
def on_msg(ch, method, props, body):
try:
handle(body) # 业务处理
ch.basic_ack(delivery_tag=method.delivery_tag) # 成功确认
except Exception:
# 失败重回队列,注意避免"失败-重投"死循环
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
ch.basic_consume("q.task", on_message_callback=on_msg, auto_ack=False)
basicReject 与 basicNack 的区别
两者语义几乎一致,唯一区别是 nack 支持 multiple 批量否定,reject 一次只能拒绝一条:
| 方法 | multiple 参数 | 单条 | 批量 |
|---|---|---|---|
| basicReject | 无 | 支持 | 不支持 |
| basicNack | 有 | 支持 | 支持(multiple=true 时否定多条未确认) |
日常开发优先用 basicNack;需要否定当前信道所有未确认消息时传 multiple=true。
未确认消息积压与连接断开重投
消费端一直不 ack,消息会以 unacked 状态积压在队列中占用内存,甚至触发 broker 告警;消费者连接断开或 channel 关闭时,这些未确认消息会被 broker 重新入队并再次投递。
要警惕:慢消费者 + 大批量手动确认的场景下,未 ack 消息堆积会拖垮内存,需配合下一章的 prefetch 限流使用。
幂等性提醒(at-least-once)
手动确认保证的是"至少一次"投递(at-least-once):消息可能在处理成功但 ack 丢失时被重复投递。因此消费逻辑必须做成幂等:用业务唯一键(订单号、消息 ID)建唯一索引,重复消息直接跳过,不要指望 broker 保证"只投一次"。
小结
确认机制决定了消息何时从队列删除:自动确认快但会丢,手动确认稳但要求处理成功必须 ack、失败用 nack/reject 决定重回队列还是转死信。配合幂等消费,才能消化"至少一次"投递带来的重复风险。