bigywater.tech
RabbitMQ · 5

发布确认:publisher confirms 的批量与单条

发布者怎么知道 broker 收下了消息?confirm 模式下 Ack 一条一个还是批量?“确认”是指消息进了队列还是落了盘?队列满了会怎样?这篇用 TCP 代理把确认帧打出来,并比较几种发布方式的吞吐。

619 个
异步发 5000 条消息时 broker 回的 Basic.Ack 个数(其中 603 个 multiple=True)
5752 → 54057
每条同步等 confirm 与异步在途上限 100 的吞吐(条/秒)
Ack 但没入队
发到没有绑定的 exchange 也会收到 Ack;mandatory=True 先 Return 后 Ack

Confirm.Select 与序号

实测 · 本机执行输出
  Confirm.Select / SelectOk 帧共 2 个(两个 channel 各开了一次);发布 10 条(pika BlockingChannel 每条同步等 ack)收到的 Basic.Ack:
    Basic.Ack tag=1 multiple=False
    Basic.Ack tag=2 multiple=False
    Basic.Ack tag=3 multiple=False
    Basic.Ack tag=4 multiple=False
    Basic.Ack tag=5 multiple=False
    Basic.Ack tag=6 multiple=False
    Basic.Ack tag=7 multiple=False
    Basic.Ack tag=8 multiple=False
    Basic.Ack tag=9 multiple=False
    Basic.Ack tag=10 multiple=False

Confirm.Select 把 channel 切成 confirm 模式,此后每条发布在该 channel 里有一个从 1 递增的序号,broker 用 Basic.Ack(成功)或 Basic.Nack(失败)按序号回复。

multiple

实测 · 本机执行输出
  发 5000 条持久化消息,在途上限 1000:耗时 164 ms;broker 一共发了 619 个 Basic.Ack,其中 multiple=True 的 603 个
  每个 Ack 确认的条数: 最小 1,中位数 7,最大 73;前 8 个 Ack: [29, 33, 12, 11, 6, 15, 7, 3]

broker 可以把已处理好的一段序号合并成一个 multiple=True 的 Ack。异步发布时 5000 条只收到 619 个 Ack,中位数每个确认 7 条,所以发布方要维护一个“在途序号集合”,按 multiple 一次性清掉 ≤ tag 的部分。

吞吐

实测 · 本机执行输出
  不开 confirm(阻塞)       132 ms     37800 条/秒
  每条同步等 confirm        869 ms      5752 条/秒
  在途上限 100              92 ms     54057 条/秒
  在途上限 1000             92 ms     54634 条/秒

每发一条就等一个 Ack,吞吐受限于往返次数(5752 条/秒);允许几十条在途后升到 54057 条/秒,100 和 1000 没区别。因此一般做“窗口式”异步发布,而不是逐条等待。

延迟

实测 · 本机执行输出
  非持久化:p50 = 0.08 ms,p99 = 0.15 ms
  持久化:p50 = 0.15 ms,p99 = 0.26 ms

单条同步 confirm 的 p50:非持久化 0.08 ms,持久化 0.15 ms。要说明的是,这是 macOS 笔记本上的数字,其 fsync 不保证落盘,不能当作生产环境的持久化延迟。

Ack 的含义

实测 · 本机执行输出
  mandatory=False 发到没有绑定的 exchange,线上帧: ['Basic.Publish', 'Basic.Ack tag=1 multiple=False']
  mandatory=True,线上帧: ['Basic.Publish', 'Basic.Return', 'ContentHeader', 'ContentBody', 'Basic.Ack tag=2 multiple=False'];pika 抛出 UnroutableError

confirm 只说明 broker 处理完这条消息,不保证进了队列:没有绑定的 exchange 同样回 Ack。要知道有没有路由到队列,要加 mandatory,此时先收到 Basic.Return 再收到 Ack。

Nack

实测 · 本机执行输出
  x-max-length=3,连发 5 条的结果: ['ack', 'ack', 'ack', 'NackError', 'NackError']
  线上 broker 发出的确认帧: ['Basic.Ack tag=1 multiple=False', 'Basic.Ack tag=2 multiple=False', 'Basic.Ack tag=3 multiple=False', 'Basic.Nack tag=4 multiple=True requeue=True', 'Basic.Nack tag=5 multiple=True requeue=True']
  队列里最终有 3 条

队列满且 x-overflow=reject-publish 时,新发布得到 Basic.Nack,发布方明确知道被拒;没开 confirm 就不知道。

分步演示

发布确认
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。线上的 Ack / Nack 帧来自自己写的 TCP 代理;第二、三节异步发布用 pika SelectConnection(在途上限用已发送数减已确认数控制),同步用 BlockingChannel.confirm_delivery;吞吐是 5000 条 100 字节的持久化消息(delivery_mode=2)到持久队列的耗时,各 3 次取中位数;延迟各 2000 条
    • 没有验证:quorum 队列和 stream 队列的确认时机(多数派落盘之后才 Ack,见第 16 篇);Linux 上真实 fsync 的持久化延迟;跨节点集群里镜像或 quorum 的确认延迟;大消息体对 confirm 延迟的影响;在途窗口大于 1000 时的内存占用;连接中途断开时在途消息的结果不确定性
    京ICP备2026067502号-1京公网安备11010502063487号