bigywater.tech
RabbitMQ · 9

公平分发与轮询分发

“轮询分发”听上去很公平,但一个快一个慢的两个消费者拿到同样多的活时,慢的会拖垮整体。prefetch 怎么把它变成“谁空闲谁拿”?卡住的消费者会扣住多少消息?消费者优先级又是怎么回事?这篇都量一遍。

9.37 s → 1.83 s
一快一慢两个消费者处理 600 条:prefetch 不限与 prefetch=1 的总耗时
= prefetch
卡住的消费者扣住的未确认消息数(1、10、100)
199 / 1
发布速率 20 条/秒时,优先级 10 与优先级 0 的消费者各拿到的条数

轮询

实测 · 本机执行输出
  投递给谁(按完成顺序前 12 条): ABABABABBAAB;总计 {'A': 21, 'B': 19}

队列里的消息依次轮流投给订阅它的消费者,但只投给“未确认数 < prefetch”的那些。速度相同时最终各约一半。

不公平的来源

实测 · 本机执行输出
  prefetch     A 快(2 ms)  B 慢(20 ms)  总耗时
  0(无限)            250         350      9.37 s
  100              458         142      3.75 s
  10               527          73      1.90 s
  1                531          69      1.83 s
  理想情况(按速度比例分,没有空转)总耗时 ≈ 600 ÷ (500 + 50) 条/秒 = 1.09 s;全部均分时总耗时 ≈ 300 × 20 ms = 6.0 s

消费者速度不同、prefetch 又很大时,慢的消费者也会持有一大批消息,总耗时被它拖长(prefetch 不限时 9.37 s)。prefetch=1 或很小时,快的消费者空出来就能立刻再拿,接近按速度比例分(1.83 s)。不限 prefetch 时的分法每次运行都不一样,我没有深究原因。

卡住的消费者

实测 · 本机执行输出
  prefetch   A 处理完(按消息去重)   队列 (ready, unacked) = 卡住的消费者扣住的
  1                499                        (0, 1)
  10               490                       (0, 10)
  100              400                      (0, 100)
  卡住的消费者断开(或超时)前,它手里的消息谁也拿不到;prefetch=1 时最多只有 1 条

消费者收到消息又不处理不确认,它扣住的消息正好是 prefetch 条,别的消费者拿不到。prefetch 越大损失越大。

x-priority

实测 · 本机执行输出
  发布速率(条/秒)   A 优先级 10   B 优先级 0
  20                     199           1
  45                     108          92
  80                     100         100
  120                    100         100
  速率低于 A 一个人的处理能力(约 50 条/秒)时,消息几乎都给 A;速率更高时,有些消息到达的瞬间 A 还在处理上一条(prefetch=1 已满),那些就改给 B

订阅时加参数 x-priority,数大的消费者先拿消息;只有它没空位(prefetch 已满)时才轮到优先级低的。适合做主备或“忙时溢出”。

加消费者

实测 · 本机执行输出
  消费者数   总耗时    每个消费者处理条数(最少~最多)
  1          10.40 s   800~800
  2           5.32 s   399~401
  4           2.74 s   198~203
  8           1.35 s   96~103

处理耗时固定、prefetch=1 时,N 个相同消费者的总耗时按 1/N 缩短,每个消费者的条数几乎相同。

分步演示

公平分发
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。每个消费者一个线程、一个连接,处理耗时用 time.sleep 模拟;第二节三个设置各跑一次(prefetch=0 的结果每次运行会不同);第四节发布速率用线程按固定间隔发,消费者每条 20 ms
    • 没有验证:prefetch=0 时分配为什么不均衡(可能与 broker 对 channel 的内部流控有关,未查);quorum 队列的分发(delivery-limit 与 consumer 数);消费者数多于队列数据量时的饥饿;单 active consumer(x-single-active-consumer);真实业务中处理耗时不稳定时 prefetch 的取值
    京ICP备2026067502号-1京公网安备11010502063487号