bigywater.tech
RabbitMQ · 26

消息顺序:多消费者下顺序怎么保持

消息在队列里是先进先出的,是不是就能保证“先发先处理”?多开几个消费者、碰上 nack 重试时,顺序会怎样?想要按账户保序又要并行,该怎么设计?这篇逐项量化。

68 → 468
2 个与 8 个消费者并发处理同一队列时的总逆序对数
5, 15, 25
prefetch=10 下被 nack 并 requeue 的三条消息,处理顺序被推迟到预取的消息之后
0 / 100
按 key 分区后,100 个账户里内部顺序乱掉的账户数

队列内有序

实测 · 本机执行输出
  100 条,1 个消费者:处理完成的顺序前 15 个 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];相邻逆序对数 0,总逆序对数 0

同一个队列里,消息按发布顺序先进先出;一个消费者按序处理就是有序的。

多消费者

实测 · 本机执行输出
  消费者数  相邻逆序  总逆序对  完成顺序(前 12 个)
  1              0         0  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
  2             66        68  [0, 1, 2, 4, 5, 3, 7, 6, 8, 9, 10, 11]
  4            113       298  [0, 3, 4, 2, 5, 1, 9, 8, 7, 6, 10, 14]
  8            109       468  [0, 5, 4, 3, 10, 9, 2, 8, 7, 6, 1, 15]

消息仍按顺序投递给不同消费者,但处理时间不同,完成顺序会乱。消费者越多越乱,实测 2 个消费者总逆序对 68,8 个 468。

requeue

实测 · 本机执行输出
  处理顺序: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
  (prefetch=1 的单个消费者上 requeue 的消息被放回队首,所以顺序没有被打乱;下面用 prefetch=10 看区别)
  prefetch=10 的处理顺序: [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 5, 16, 17, 18, 19, 20, 21, 22, 23, 24, 15, 26, 27, 28, 29, 25]

prefetch=1 时被 requeue 的消息回到队首,顺序不变;prefetch 较大时,被 requeue 的消息会晚于已预取的消息被处理。

按 key 分区

实测 · 本机执行输出
  处理了 1000 条;100 个账户里,每个账户内部顺序乱掉的有 0 个(按 key 分区后,同一个账户总是进同一个队列、同一个消费者)
  这 100 个账户被分到 8 个队列里的分布: [12, 12, 12, 12, 12, 12, 14, 14]

要同时保序和并行,就按 key(如账户)哈希分到多个队列,每个队列一个消费者:同 key 有序,不同 key 不保证;并行度受分区数限制。

分步演示

消息顺序
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。消费者是线程里的 pika 连接,处理耗时用 time.sleep 模拟(基础 10 ms 的 1~5 倍,按序号确定性地变化);逆序对是对完成顺序统计;分区实验用 direct exchange + 8 个队列 + crc32 取模
    • 没有验证:单活跃消费者(x-single-active-consumer)保序;一致性哈希 exchange 插件分区;quorum / stream 队列上的同样现象;发布端多个并发发布者之间的顺序(这里只有一个发布者);消费者故障转移后的顺序;重试队列(死信回环)对顺序的破坏
    京ICP备2026067502号-1京公网安备11010502063487号