rabbitmq默认轮询分发会导致负载不均,需启用basicqos(prefetch_count=1)实现公平分发;高能力消费者可调大--timeout、减小--sleep,低性能者应降低prefetch_count;严禁混用多队列名模拟优先级,须为不同优先级创建独立队列并单独监听。

RabbitMQ 默认轮询分发会导致负载不均
当多个 queue:work 进程监听同一个 RabbitMQ 队列时,RabbitMQ 默认按轮询(Round-Robin)方式派发消息:第1条给消费者A、第2条给B、第3条再给A……不管每个消费者当前是否空闲或处理快慢。这在任务耗时差异大时非常危险——比如一个消费者卡在慢 SQL 上,另一个刚处理完就空转,但新消息仍被继续塞给忙碌者。
必须启用 basicQos 实现公平分发
核心是让 RabbitMQ 等待消费者确认(ack)后再投递下一条,而不是“一发了之”。Laravel 的 vladimir-yuldashev/laravel-queue-rabbitmq 包默认已支持该行为,但需显式开启:
- 确保配置中
'qos' => ['prefetch_size' => 0, 'prefetch_count' => 1, 'global' => false]已启用(这是关键) - 不要手动调用
channel->basic_qos()—— 包内部已在消费者启动时自动设置 - 确认任务执行后有明确的
ack(即不抛异常、不超时、不手动reject)
此时 RabbitMQ 会始终把下一条消息发给「尚未确认上一条」的空闲消费者,实际效果接近“谁空闲谁接单”。
不同消费者处理能力差异大时要手动干预
即使开了 prefetch_count = 1,若一个消费者因环境差异(如 CPU/IO 限制)长期比其他慢,它仍会持续积压未确认消息,导致整体吞吐下降。这时不能只靠 RabbitMQ 调度:
- 给高能力消费者分配更高
--timeout和更短--sleep(例如php artisan queue:work rabbitmq --queue=heavy --timeout=300 --sleep=1) - 对低性能机器上的消费者,主动降低其
prefetch_count值(如设为1或保持默认),避免它一次领多条却卡住 - 监控各消费者的
unacknowledged消息数(通过rabbitmqctl list_queues name messages_unacknowledged),持续高于 5 就需排查或降权
不要混用队列名和连接名来“模拟”优先级
有人试图用 onQueue('high,low') 让一个 worker 同时监听多个队列,这在 RabbitMQ 场景下完全失效——因为 Laravel 的 RabbitMQ 驱动不支持多队列轮询调度,它只会固定绑定第一个队列名;且一旦 high 队列里有长任务,low 的消息就永远卡住。
真正可行的做法是:为不同优先级或业务模块创建独立队列(如 high、notifications、exports),再为每个队列启动专属 queue:work 进程,并用 Supervisor 分别管理。否则所谓“公平”,只是假象。
最易被忽略的一点:RabbitMQ 的公平分发依赖消费者主动 ack,而 Laravel 默认在 Job 执行成功后才 ack。如果 Job 内部有阻塞操作(如 sleep(30) 或未超时的 cURL),它会一直占用通道,导致其他消费者饿死——这不是配置问题,是代码逻辑问题。











