Laravel负载均衡场景下运行队列出现任务重复执行报错问题求助
问题根因
该问题的核心是database队列的任务保留时间配置和任务实际执行时长不匹配,导致第一个worker还在执行任务时,队列系统已经将未完成的保留任务判定为失败,释放给第二个worker拾取执行,同时触发任务执行次数/时长超限报错。
你之前用Queue::before未生效的核心原因是没有基于jobs表的reserved_at字段做正确的执行状态校验。
可落地解决步骤
1. 先对齐三个核心配置,避免配置冲突
这是90%以上同类问题的出错点,三个配置的数值必须满足retry_after > 任务timeout的规则:
- 任务类内部的
$timeout属性:设置为你任务实际最大可能执行时长的1.2倍以上,比如任务最长跑10分钟,设置为public $timeout = 720;(单位为秒) - 队列启动命令的
--timeout参数:必须和任务类的$timeout数值完全一致,示例启动命令:php artisan queue:work database --timeout=720 config/queue.php中database队列的retry_after参数:必须比上面的timeout数值大至少30秒,比如上面timeout为720,此处设置为'retry_after' => 780,,确保任务真的异常退出后才会被释放给其他worker重试。
2. 长耗时任务增加独占锁避免重复执行
如果你的任务本身执行时长超过1小时,或者无法准确预估最大执行时长,可以加全局共享的原子锁避免重复执行,注意缓存驱动必须使用所有worker共享的redis/memcached,不能用本地file/array驱动:
public function handle() { // 以任务ID作为唯一锁key,锁过期时间设置为你能接受的任务最长执行时长 $lock = Cache::lock('long_job_lock_'.$this->job->getJobId(), 7200); // 抢不到锁说明已经有其他worker在执行该任务,直接退出即可 if (!$lock->get()) { return; } try { // 原有任务执行逻辑 } finally { // 任务执行完主动释放锁 optional($lock)->release(); } }
3. 修正Queue::before的校验逻辑
如果你要保留Queue::before的兜底逻辑,需要按照以下写法实现:
在app/Providers/AppServiceProvider.php的boot方法中添加:
use Illuminate\Queue\Events\JobProcessing; use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\DB; public function boot() { Queue::before(function (JobProcessing $event) { $jobId = $event->job->getJobId(); $retryAfter = config('queue.connections.database.retry_after'); // 判断当前任务是否还在有效保留期内,是则直接删除当前重复拾取的任务 $exists = DB::table('jobs') ->where('id', $jobId) ->where('reserved_at', '>', now()->timestamp - $retryAfter) ->exists(); if ($exists) { $event->job->delete(); } }); }
4. 额外排查点
检查两台服务器的系统时间是否同步,如果节点之间时间差超过30秒,会导致reserved_at字段的时间判断失效,出现任务重复拾取的问题。
内容的提问来源于stack exchange,提问作者fake97
相关产品推荐
相关产品推荐

