Laravel Queues/Horizon:如何在多Worker服务器间均匀分配任务
背景
我有一个基于Laravel 9.x的应用,通过Horizon运行多个资源消耗极大的任务类,目前部署了3台专用Worker服务器运行Horizon,所有服务器连接同一中央Redis实例拉取任务,正在探索队列Worker集群的水平扩展方案。
存在的问题
假设有一个ProcessDocumentJob任务类,3台Worker服务器均监听同一document-processing队列。当我将该任务分发3次到队列时,经常出现一台服务器承接全部3个任务,另外两台闲置的情况,无法充分利用所有服务器的性能。我希望任务能像Web流量负载均衡轮询那样,在所有Worker服务器间均匀分配。
当前解决方案
我给每台服务器设置专属的WORKER_QUEUE环境变量,对应3个不同队列(worker-queue-1、worker-queue-2、worker-queue-3),同时用HORIZON_ENV环境变量设置为"worker"指定对应环境。
horizon.php配置片段
'environments' => [ 'worker' => [ 'worker-supervisor' => [ 'connection' => 'redis', 'queue' => [env('WORKER_QUEUE')], 'balance' => 'simple', 'maxProcesses' => 10, 'maxJobs' => 0, 'memory' => 512, 'tries' => 3, 'nice' => 0, 'timeout' => 600, ], ], ]
ProcessDocumentJob构造函数
public function __construct(Document $document) { $this->onQueue(Worker::bestPick()); $this->document = $document; }
Worker::bestPick()是我自己实现的轮询分发逻辑,它会根据可用Worker数量、各服务器对应队列名称以及上一次使用的队列,确定下一个任务分配到哪个队列。
方案缺陷
这个方案多数情况能实现任务均匀分配,但如果某台Worker服务器宕机,除非在Worker::bestPick()中加入可用性检测逻辑,否则会失效。另外我尝试过Redis原子锁和限制maxJobs字段,都没达到预期效果。
咨询问题
- 如何实现类似Web流量负载均衡轮询的方式,在所有Worker服务器间均匀分配任务?
- 以峰值性能为目标的Worker服务器水平扩展思路是否正确?有没有更贴合Laravel生态的实现方式?
备注:我已了解队列FIFO特性、
balance字段的可选值("simple"、"auto"、"false")无法解决当前问题,且已阅读Laravel官方文档中Horizon和队列相关内容。
解答
问题1:实现Worker服务器间的轮询式任务分配
不用自己维护队列映射和轮询逻辑,可以采用以下两种更可靠的方式:
- 利用Redis消费者分组:Redis 5+支持消费者分组特性,Laravel的Redis队列驱动也兼容该功能。将所有Worker服务器加入同一个消费者组,Redis会自动将任务分发给空闲的消费者,天然实现负载均衡。配置时让所有Worker监听同一队列,在Horizon的supervisor配置中启用消费者分组:
这种方式下,Redis会跟踪每个消费者的任务处理状态,自动分配任务给空闲Worker;Worker宕机后,未完成的任务会被重新分配给存活节点,无需手动维护轮询逻辑。'worker-supervisor' => [ 'connection' => 'redis', 'queue' => ['document-processing'], 'balance' => 'auto', 'maxProcesses' => 10, 'consumer_group' => 'document-processing-group', 'memory' => 512, 'tries' => 3, 'timeout' => 600, ] - 健康检测+动态轮选:改进现有轮询逻辑,加入Worker健康检测。让每个Worker服务器定时向Redis写入心跳(比如每秒更新
worker:{id}:last-seen键值),Worker::bestPick()在选择队列前,先过滤掉超过10秒未更新心跳的Worker,再从存活节点对应的队列中轮选,自动排除宕机服务器。
问题2:峰值性能导向的水平扩展思路及Laravel生态方案
你的扩展思路是正确的,结合Laravel生态工具可进一步优化:
- Horizon+容器编排弹性伸缩:Horizon的
balance=auto模式可在单服务器内根据队列长度调整进程数,跨服务器的水平伸缩可结合Kubernetes,通过监控Redis队列任务长度指标,配合HPA(Horizontal Pod Autoscaler)自动增减Worker Pod数量,实现基于负载的弹性扩展。 - 任务分片优化:将大任务拆分为多个小任务,比如把
ProcessDocumentJob拆成ExtractTextJob、AnalyzeContentJob等细粒度任务,让更多Worker同时处理,提升整体吞吐量,也更容易实现负载均衡。 - Laravel Octane优化Worker效率:如果Worker进程启动开销大,可用Laravel Octane运行Horizon,基于Swoole或RoadRunner复用进程,减少进程启动的资源消耗,提升Worker处理效率,间接提升集群峰值性能。
内容的提问来源于stack exchange,提问作者Denis Priebe

