高可用部署下Laravel Horizon任务重复执行故障咨询
问题
在高可用架构下部署了两个Laravel应用,通过负载均衡器分配流量。两个应用共享同一AWS ElasticCache Redis数据库作为队列存储,且均配置了Laravel Horizon,每个应用包含3个分别对应high、medium、low队列的Worker。
多数任务运行正常,但有一个耗时较长的任务出现异常:该任务由一个Horizon实例开始处理后,仅过1分30秒,第二个Horizon实例也会取出并执行该任务。由于此任务不支持并行执行,最终导致任务失败。
疑问:
- Horizon是否自带锁机制?还是需要自行实现?
- 为何第一个Horizon取出任务90秒后,第二个实例会再次取出该任务?
附配置文件:config/horizon.php
'environments' => [ 'production' => [ 'supervisor-1' => [ 'connection' => 'redis', 'queue' => ['high', 'default', 'low'], 'balance' => 'auto', 'processes' => 1, 'tries' => 1, 'timeout' => 1250, 'memory' => 2048, ], ], ],
config/queue.php
'connections' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => 'default', 'retry_after' => 1260, 'block_for' => null, ], ],
解答
为什么90秒后任务被重复执行?
- Laravel版本兼容问题:如果你的Laravel版本是7或更早,Redis队列的任务锁默认超时为90秒,即便配置了
retry_after=1260也不会覆盖这个默认值。锁过期后,其他Worker就会认为任务未被处理,进而取出重复执行。 - 锁刷新失败:Laravel Worker会在任务执行期间定期刷新锁(通常在锁时长的一半时间点触发),如果Worker进程因内存不足、假死等原因无法完成锁刷新,锁会提前过期,导致任务被重复取出。
Horizon的锁机制说明
Horizon本身没有独立的锁机制,它完全依赖Laravel队列原生的锁功能来避免任务重复执行。对于不支持并行的任务,更可靠的方式是使用Laravel内置的WithoutOverlapping特性,而非仅依赖队列锁。
解决建议
调整锁超时配置(针对Laravel 7及更早版本):
在config/queue.php的Redis连接中添加lock_timeout配置,值与retry_after保持一致:'redis' => [ // 其他配置 'retry_after' => 1260, 'lock_timeout' => 1260, // 添加这一行 'block_for' => null, ],为任务添加无重叠限制:
直接在任务类或分发任务时调用withoutOverlapping()方法,确保同一任务绝不会并行执行:// 方式1:在任务类的handle方法中 public function handle() { $this->withoutOverlapping(); // 你的任务逻辑代码 } // 方式2:分发任务时 YourLongRunningTask::dispatch()->withoutOverlapping();验证Worker状态:
检查Horizon的监控面板,确认处理该任务的Worker是否存在进程异常(如频繁重启、内存占用过高),避免因Worker异常导致锁无法正常刷新。
内容的提问来源于stack exchange,提问作者Atnaize
相关产品推荐
相关产品推荐

