Laravel:使用Supervisor运行链式任务队列的问题(需任务链按顺序执行而非并行)
你当前的代码是循环dispatch四个独立的任务链,这会导致四个FakeA任务被立即全部入队。而Laravel链式任务的后续任务(FakeB)只有在前一个任务(FakeA)完成后才会入队。由于database队列默认按created_at顺序执行,四个FakeA的入队时间都早于后续生成的FakeB,所以Worker会先把所有FakeA处理完,再处理FakeB——这就是你看到日志顺序的核心原因。
下面给出两种针对性解决方案,分别匹配你的单进程和多进程需求:
方案一:单进程严格串行(完全匹配单进程期望)
把所有站点的任务合并成一个大的链式任务,这样执行流程会严格按照「A1→B1→A2→B2→A3→B3→A4→B4」的顺序执行。
修改你的FakeQueue命令代码:
class FakeQueue extends Command{ public function handle() { $server_id = $this->argument('server_id'); $queue_name = 'backups_server_'.$server_id; $reference_uuid = (string) Str::orderedUuid(); $sites = [1, 2, 3, 4]; // 构建完整的串行任务链 $fullTaskChain = []; foreach($sites as $site) { $chain_uuid = (string) Str::orderedUuid(); $fullTaskChain[] = new FakeA($site, $chain_uuid, $reference_uuid); $fullTaskChain[] = new FakeB($site, $chain_uuid, $reference_uuid); } // 调度整个大链 Bus::chain($fullTaskChain) ->onQueue($queue_name) ->onConnection('database') ->dispatch(); $command = 'supervisorctl start server-'. $server_id . '-backups-worker:*'; shell_exec($command); } }
原理
大链式任务只会先入队第一个任务(A1),A1完成后才会入队B1,B1完成后入队A2,以此类推。单进程Worker会严格按这个顺序执行,完全符合你的单进程期望。如果是多进程,由于链式任务是串行依赖的,只有一个Worker会处理整个链,其他Worker会处于空闲状态直到整个链完成。
方案二:多进程并行+站点内串行(匹配多进程期望)
如果你希望多进程时能同时处理不同站点的任务链,但每个站点的A→B必须串行,且Worker处理完一个站点的B后再取下一个站点的A,核心是让每个站点的FakeB任务**优先于其他站点的FakeA**被执行。
步骤1:给database队列添加优先级支持
默认的Laravel database队列没有优先级字段,先给jobs表添加priority字段:
// 生成迁移文件 php artisan make:migration add_priority_to_jobs_table --table=jobs // 迁移文件内容 public function up() { Schema::table('jobs', function (Blueprint $table) { $table->integer('priority')->default(0); }); } public function down() { Schema::table('jobs', function (Blueprint $table) { $table->dropColumn('priority'); }); } // 执行迁移 php artisan migrate
步骤2:修改任务调度逻辑,给FakeB设置更高优先级
在调度任务链时,给FakeB指定高优先级队列:
class FakeQueue extends Command{ public function handle() { $server_id = $this->argument('server_id'); $queue_name = 'backups_server_'.$server_id; $high_priority_queue = $queue_name.':high'; // 高优先级队列名称 $reference_uuid = (string) Str::orderedUuid(); $sites = [1, 2, 3, 4]; foreach($sites as $site) { $chain_uuid = (string) Str::orderedUuid(); $jobChain = [ new FakeA($site, $chain_uuid, $reference_uuid), // 给FakeB指定高优先级队列 (new FakeB($site, $chain_uuid, $reference_uuid))->onQueue($high_priority_queue) ]; Bus::chain($jobChain) ->onQueue($queue_name) ->onConnection('database') ->dispatch(); } $command = 'supervisorctl start server-'. $server_id . '-backups-worker:*'; shell_exec($command); } }
步骤3:修改Worker启动命令,优先处理高优先级队列
在你的Supervisor配置中,修改Worker的启动命令,让它先监听高优先级队列,再监听普通队列:
command=php /path/to/your/project/artisan queue:work database --queue=backups_server_%(program_num)s:high,backups_server_%(program_num)s
(注意替换%(program_num)s为你的server_id变量,或者根据你的Supervisor配置调整)
原理
当某个站点的FakeA完成后,对应的FakeB会被入队到高优先级队列。Worker会优先处理高优先级队列的任务,所以会先完成当前站点的FakeB,再去处理其他站点的FakeA。这样多进程场景下:
- Worker1处理A1 → 完成后处理B1 → 再处理A3
- Worker2处理A2 → 完成后处理B2 → 再处理A4
完全符合你的多进程期望。
内容的提问来源于stack exchange,提问作者urka_mazurka

