bigywater.tech
RabbitMQ · 4

消息确认:ack、nack、reject 与 requeue

ack、nack、reject 三个动作和 requeue 这个开关,到底分别让消息去哪?拒绝的消息回到队首还是队尾?确认可以换个 channel 做吗?消费者崩溃时手动确认和自动确认差在哪?一个总是 requeue 的消费者会造成什么?这篇用 rabbitmqctl 实时读 ready / unacked 数,一项项量。

(2, 3)
取走 3 条不 ack 之后的 (ready, unacked):消息只是“未确认”,没有消失
406
用另一个 channel 确认别人收到的 delivery tag,broker 回的错误码
11967 次
一条毒消息在 1 秒内被 nack(requeue=True) 反复投递的次数

未确认状态

实测 · 本机执行输出
  初始 (ready, unacked) = (5, 0)
  basic_get 取 3 条(m1 m2 m3),不 ack: ['m1', 'm2', 'm3'];(ready, unacked) = (2, 3)
  ack m1 之后: (ready, unacked) = (2, 2)
  对 m3 的 tag 做 ack(multiple=True),m2 也一并确认: (ready, unacked) = (2, 0)

消息投递给消费者后进入 unacked,ready 和 unacked 之和不变。只有 ack 才让 broker 删除它;multiple=True 一次确认到这个 tag 为止的所有消息。

requeue 回到哪

实测 · 本机执行输出
  取到 m1、m2,redelivered=False,False
  nack(m1, requeue=True) 之后继续取 4 条的顺序: [('m1', True), ('m3', False), ('m4', False), ('m5', False)](m2 还没确认,不在里面)
  channel 关闭后全部未确认的消息回到队列: (ready, unacked) = (5, 0)
  再取全部(顺序和 redelivered 标志): [('m1', True), ('m2', True), ('m3', True), ('m4', True), ('m5', True)]

nack / reject 加 requeue=True 时消息放回队列原来的位置(这里是队首),redelivered 标志变成 True;channel 或连接关闭时所有未确认的消息自动回队列,顺序保持不变。

丢弃与批量

实测 · 本机执行输出
  reject(m1, requeue=False): (ready, unacked) = (0, 5)
  nack(m4, multiple=True, requeue=False) 把 m2~m4 一起丢: (ready, unacked) = (0, 2)
  nack(m6, multiple=True, requeue=True) 把 m5、m6 放回: (ready, unacked) = (2, 0)
  队列里剩下: [('m5', True), ('m6', True)]

reject 一次一条;nack 是 RabbitMQ 支持的扩展,能加 multiple=True 一次处理一批。requeue=False 在没有配置死信交换机时就是丢弃。

delivery tag

实测 · 本机执行输出
  channel A 收到 tag=1 的消息;channel B 用同一个 tag 去 ack → B 被关: ["(406, 'PRECONDITION_FAILED - unknown delivery tag 1')"]
  channel A 仍可确认: ok;(ready, unacked) = (1, 0)

delivery tag 是每个 channel 自己的递增编号。换一个 channel 用同一个 tag 去确认会得到 406 PRECONDITION_FAILED,并且那个 channel 被关闭。

自动确认

实测 · 本机执行输出
  auto_ack=True:取走 3 条后连接关闭 → (ready, unacked) = (1, 0)
  auto_ack=False:取走 3 条后连接关闭 → (ready, unacked) = (4, 0)

auto_ack(no_ack)时消息一投出去就算确认,消费者崩溃时在途的消息就丢了;手动确认时这些消息回到队列,代价是可能被重复投递(后面有一篇讲幂等)。

毒消息

实测 · 本机执行输出
  1 秒内同一条消息被投递了 11967 次;第 1 次 redelivered=False,之后全部 redelivered=True
  经典队列只带一个 redelivered 布尔值,没有计数——数不出已经重试了几次(quorum 队列有 x-delivery-count,见后面的文章)

总是 requeue 的消费者会让同一条消息不停地循环。经典队列只有一个 redelivered 布尔值,无法限制次数。

分步演示

消息确认
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / RabbitMQ 4.3.6 / Erlang/OTP 28.5 / pika 1.4.4(Homebrew 与临时虚拟环境);所有节点都是本机自己起的 RabbitMQ 进程(127.0.0.1,端口 9500 以上,每个实验独立的数据目录与节点名,不连任何外部服务);命令输出来自真实的 rabbitmqctl 与管理 API,发布与消费用 pika 的真实连接;计时用 time.perf_counter,数字每次运行会有小幅抖动。ready / unacked 数来自 rabbitmqctl list_queues(实时,管理 API 的统计有几秒延迟);第一~五节用 basic_get 取消息以保证顺序可复现,第六节用 basic_consume 加回调;每个小实验开始前 purge 并重新发布
    • 没有验证:quorum 队列和 stream 队列上同样操作的行为(requeue 位置、delivery-count);带 prefetch 的推送消费者下 requeue 的顺序(这里用 basic_get 和单个 consumer);批量 nack 的确认跨过已经 ack 过的 tag 的行为;consumer_timeout 对未确认消息的影响(见后面一篇);多个消费者之间的 requeue 公平性
    京ICP备2026067502号-1京公网安备11010502063487号