You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel Horizon队列问题:仅保留一个任务,后续任务需取消

解决Laravel队列任务重复执行的问题

你的问题核心在于判断队列是否为空的逻辑不准确,而且对withoutOverlapping的作用理解有偏差,我来帮你拆解并解决:

为什么当前方案不生效?

  1. withoutOverlapping()是用来防止调度的Command命令本身重叠执行,而不是控制队列里的任务只能有一个在运行。比如你的Command如果1秒就执行完(判断队列空→发任务→结束),那下一分钟的调度还是会正常触发,不会被阻止。
  2. 你用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:08:59