Laravel 13.31 增加了可中断任务(interruptible jobs),允许队列 worker 在部署期间捕获发送的 SIGTERM 信号,从而实现平滑关闭,而不是在中途中止任务。对于运行长时间任务的团队——例如处理数万行数据、批量发送邮件或生成报告——这一功能非常有益,因为在发布新版本时可以确保数据的一致性。

为什么部署会导致任务被杀死

当新版本上线时,Supervisor、Docker 和 Kubernetes 等进程管理器会通过发送 SIGTERM(一个终止进程的礼貌请求)来通知现有 worker 停止工作。大多数 worker 会等待当前任务完成后再退出。问题在于,如果管理器只给予几秒钟的响应时间,而任务需要几分钟才能完成,管理器就会升级为 SIGKILL,立即杀死进程。这会导致任务无法达到一致状态,留下部分更新的记录、孤儿文件或重复的工作。

Laravel 的解决方案:协作式中断

Laravel 13.31 引入了一个名为 Interruptible 的契约(contract),任务类可以通过实现该契约来感知终止请求。当 SIGTERM 到达时,Laravel 会调用任务的 interrupted() 方法。框架不会自动停止任务;任务必须自行检查 Laravel 设置的标志并退出。这种协作模型允许开发者在退出前完成当前的循环迭代、回滚部分更改或写入检查点。

如何使任务可中断

  1. 实现契约 – 在任务类定义中添加 implements Interruptible。
  2. 在工作循环中检查标志 – 在每次迭代中插入一个检查,以判断任务是否应该停止。
  3. 执行清理工作 – 在 interrupted() 方法中,持久化任何允许任务稍后恢复的状态,或者记录中断情况以便后续分析。

最大的陷阱:不要使用 --once

使用 php artisan queue:work --once 运行队列 worker 会完全禁用信号处理。该 worker 处理单个任务后即退出,但它从未注册 SIGTERM 处理器。因此,任何可中断的任务都会忽略终止请求并在处理过程中被杀死。这种模式常见于 cron 驱动的容器和一次性部署中。解决办法是:只要需要使用可中断任务,就请运行守护进程模式 (php artisan queue:work)。

监控中断情况

Laravel 触发了两个你可以挂载(hook)的事件:

  • WorkerInterrupted – 每当任何 worker 接收到 SIGTERM 时触发。记录此事件可以从宏观角度了解部署中断 worker 的频率。
  • JobInterrupted – 仅当当前任务实现了 Interruptible 契约时才会触发。使用此事件来触发任务特定的清理工作,例如删除临时文件或更新“最后处理行”标记。

通过监听这些事件,团队可以构建仪表板,展示部署对任务的影响,并发现那些频繁被中断的任务。

队列大小报告功能得到改进

此次发布为队列管理器增加了 totalSize() 方法,用于返回所有队列中等待中的任务总数。对于报告实际计数的数据库驱动和 Redis 驱动,该值是可靠的。而 SQS、Sync 和 Beanstalkd 等驱动程序仍会返回零,因为它们没有通过 Laravel 的 API 暴露计数。使用 SQS 的团队应继续依赖 CloudWatch 指标来查看队列深度。

这对不同环境意味着什么

  • Docker/Kubernetes – 现在可以有效地利用平滑关闭阶段(graceful shutdown period)。延长终止宽限期,以便任务有几秒钟的时间注意到标志并平滑退出。
  • Supervisor – 将 stopwaitsecs 设置为高于你预期任务在收到中断标志后完成所需的时间。
  • 遗留任务 – 审查任何运行时间超过管理器超时时间的任务,并在可行的情况下使其变为可中断。

反方观点:增加了复杂性

该功能并不能取代正确的超时配置。如果任务的循环从未检查中断标志,管理器仍然会发送 SIGKILL。团队必须审计长时间运行的代码路径,并在逻辑断点处插入检查。对于那些已经在关闭窗口期内完成的短任务,一些开发者可能会觉得实现契约并添加标志检查所带来的额外样板代码并不划算。

行动清单

  1. 扫描部署脚本中的 --once 标志,并在使用可中断任务的地方将其替换为守护进程模式。
  2. 识别运行时间最长的任务(即处理数万行数据或运行数分钟的任务),并添加 Interruptible 契约。
  3. 在每次循环迭代中插入标志检查,并将任何必要的收尾代码移至 interrupted() 方法中。
  4. 为 WorkerInterrupted 和 JobInterrupted 挂载监听器,以收集部署对处理过程影响频率的指标。
  5. 对于 Redis 或数据库队列,使用 totalSize() 监控积压情况;对于 SQS,保留现有的 CloudWatch 警报。

将部署驱动的停机视为一个协调一致的步骤,而非突然的强行终止。Laravel 13.31 能够保持数据一致性,并减轻处理半完成任务带来的运维难题。虽然代价是代码职责略有增加,但依赖重度后台处理的团队将获得更可靠的生产环境。