编程 Laravel 13.40 长任务回调:队列阈值是毫秒,定时任务阈值是秒

2026-11-05 21:03:44

Laravel 13.40 长任务回调:队列阈值是毫秒,定时任务阈值是秒

Laravel v13.40.0 发布。这个版本给耗时过长的队列 job 和定时任务加了回调,给读副本加了 commit ID 校验,中间件栈支持 insertBefore() / insertAfter(),请求输入辅助方法加了 strict 标志,另外修了 Concurrency 进程驱动的一个安全问题。

  • Queue::whenJobRunsLongerThan() 与 Schedule::whenTaskRunsLongerThan()
  • DB::getLastCommitId() / DB::hasCommitId() 用于读副本
  • 在内置中间件之前或之后插入中间件
  • integer()、float()、boolean()、string() 的 strict 标志
  • Concurrency::run() 使用 process 驱动时对结果输出签名
  • 连接丢失与锁等待超时的事务修复

长耗时队列 job 与定时任务的告警

Jack Bayliss 加了 Queue::whenJobRunsLongerThan():当一次 job attempt 耗时超过阈值时执行回调。Laravel 此前已有 DB::whenQueryingForLongerThan() 以及针对请求、命令的同类方法,但队列 job 一直没有。

use Carbon\CarbonInterval;
use Illuminate\Support\Facades\Queue;

Queue::whenJobRunsLongerThan(CarbonInterval::seconds(90), function ($job, $duration) {
    Log::warning("{$job->resolveName()} took {$duration}ms");
});

回调监听 JobAttempted 事件,因此失败和被 release 的 attempt 同样会触发。直接传数字时按毫秒读取,回调收到的 $duration 也是毫秒。

第二个 PR 加了 Schedule::whenTaskRunsLongerThan(),用于定时任务;它在任务结束或失败之后执行,而这里直接传数字时按秒读取:

use Carbon\CarbonInterval;
use Illuminate\Console\Scheduling\Event;
use Illuminate\Support\Facades\Schedule;

Schedule::whenTaskRunsLongerThan(CarbonInterval::minutes(45), function (Event $task, float $runtime) {
    Log::warning("Scheduled task [{$task->getSummaryForDisplay()}] took {$runtime}s");
});

两组 API 签名几乎一样,唯独裸数字的单位不一致:Queue::whenJobRunsLongerThan(30000, ...) 是 30 秒,Schedule::whenTaskRunsLongerThan(30000, ...) 是 30000 秒。用 CarbonInterval 可以绕开这个差异。详见 #62141 和 #62233。

触发时机与覆盖范围

Queue::whenJobRunsLongerThan(30000, fn ($job, $ms) => ...) 写在 service provider 的 boot() 里即可。它本质上是 JobAttempted 的监听器,读取 13.39 加入的 duration,因此同时覆盖 worker 和 sync 队列;抛异常的 attempt 和正常返回的 attempt 都算。

$job 是队列 job 对象,不是你自己的 job 类,取名字要用 $job->resolveName()。

它只在 attempt 结束之后触发。仍在运行的 job 还没走到这一步;被 --timeout 杀掉的 job 永远不会触发——worker 在 JobAttempted 派发之前就已经死了,所以最慢的那批 attempt 恰好是它漏掉的。

与数据库版本的区别:DB::whenQueryingForLongerThan() 是在一个请求的总查询时间越过阈值时触发一次;队列这个是每次 attempt 单独判断,每一次超阈值都会触发。

读副本的 commit ID

Tobias Petry 加了判断读副本是否已收到某次写入的方法。Laravel 13.38 引入的 PreventStaleDatabaseReads 中间件是在写入后固定一段时间内把读请求发到写服务器;有了 commit ID,可以直接问副本,而不是猜延迟有多久:

use Illuminate\Support\Facades\DB;

// After a write, on the primary
$order = $user->orders()->create($attributes);
$commitId = DB::getLastCommitId();

// Later, on a replica
DB::hasCommitId($commitId);          // Check now
DB::hasCommitId($commitId, wait: 2); // Wait up to two seconds

mergeCommitIds() 把多个 commit ID 合成一个,可以一次性等待全部。

驱动前提:MySQL 需要开启 GTID,MariaDB 需要开启 binary log,PostgreSQL 可用但等待功能要求 PostgreSQL 19。其他驱动调用会抛 RuntimeException。详见 #62198。

queue:work 把 release 的 job 打印成 RELEASED

调用了 $this->release()、或被 RateLimited、WithoutOverlapping 之类中间件放回队列的 job,其实是正常返回的,因此 queue:work 以前为这种注定要再跑一次的 attempt 打印绿色的 DONE。现在改打印黄色的 RELEASED。

异常之后再 release 仍然打印黄色的 FAIL,--json 输出本来就是 "result":"released",这两处都不变。代价是:任何统计 worker 日志里 DONE 行数的东西,数字都会变少,因为 release 不再计入其中。

其他改动

  • 调度器在任务已于另一台服务器执行(onOneServer())时派发 ScheduledTaskSkipped。
  • BelongsToMany 上的 createOrFirst() 会在写连接上查找该行。
  • 模型缺失的 batch job 会被记录为 failed,而不是静默丢弃。
  • 事务修复覆盖连接丢失与锁等待超时两种情况。
复制全文 生成海报 Laravel PHP 队列 MySQL PostgreSQL

推荐文章

程序员茄子在线接单