Laravel Horizon队列问题:仅保留一个任务,后续任务需取消
解决Laravel队列任务重复执行的问题
你的问题核心在于判断队列是否为空的逻辑不准确,而且对withoutOverlapping的作用理解有偏差,我来帮你拆解并解决:
为什么当前方案不生效?
withoutOverlapping()是用来防止调度的Command命令本身重叠执行,而不是控制队列里的任务只能有一个在运行。比如你的Command如果1秒就执行完(判断队列空→发任务→结束),那下一分钟的调度还是会正常触发,不会被阻止。- 你用
Redis::lrange('queues:source', 0, -1)判断队列长度,这个只能看到队列中等待被Worker取走的任务。当Worker已经把任务取走并正在执行时,队列里是空的,但任务其实还在运行,这时候下一次调度的Command就会误以为可以发新任务,导致多个任务同时运行,Horizon里自然会看到多个任务。
正确的解决方案:判断任务是否正在运行
我们需要直接检查是否有该Job正在执行,而不是看队列是否为空,推荐用缓存标记的方式,简单可靠:
步骤1:修改Command中的分发逻辑
用缓存键标记任务是否在运行,只有当标记不存在时才分发任务:
// 替换原来的队列判断逻辑 if (!\Illuminate\Support\Facades\Cache::has('job:source:running')) { dispatch((new \App\Jobs\Source())->onConnection('redis')->onQueue('source')); }
步骤2:修改Job的handle方法,添加运行标记的生命周期
在任务开始时设置缓存标记,任务结束(无论成功还是失败)时删除标记:
public function handle() { try { // 设置缓存标记,过期时间要比任务执行时间长(比如比sleep的2000秒多1000秒) \Illuminate\Support\Facades\Cache::put('job:source:running', true, now()->addSeconds(3000)); dump('job work'); sleep(2000); } finally { // 不管任务成功还是抛出异常,都要删除标记,避免永久锁住 \Illuminate\Support\Facades\Cache::forget('job:source:running'); } }
额外优化:保留调度的withoutOverlapping
虽然我们已经在Command里做了判断,但withoutOverlapping()可以保留,防止极端情况下Command本身重叠执行(比如某次Command因为某些原因卡住了),让调度更安全:
$schedule->command('source')->withoutOverlapping()->everyMinute();
这样修改后,无论队列是否为空,只要有Source任务在运行,后续的调度就不会分发新任务,Horizon里就只会看到一个任务在运行了。
内容的提问来源于stack exchange,提问作者cryptodoxer
相关产品推荐
相关产品推荐

