laravel queue:work默认不响应sigterm是因为--daemon模式禁用ticks机制,导致pcntl_signal注册的处理器无法调度;必须手动在启动时注册信号处理器并每轮循环调用pcntl_signal_dispatch()才能捕获信号。

为什么 queue:work 默认不响应 SIGTERM
Laravel queue:work 进程在默认 CLI 模式下(非 --daemon)其实会响应 SIGTERM,但前提是它没被包装进其他进程或容器中屏蔽了信号;而真正常见问题出在 --daemon 模式——该模式禁用了 PHP 的 ticks 机制,导致 pcntl_signal() 注册的处理器无法被及时调度,信号被忽略。你看到 kill -TERM {PID} 没反应,并不是 Laravel 不支持,而是底层信号没被消费。
怎样让 queue:work 真正捕获 SIGTERM
必须手动补上信号监听逻辑,且不能依赖框架默认行为。核心是:在 Worker 启动时显式注册处理器,并在主循环中主动调度信号分发。
- 创建自定义命令(如
app/Console/Commands/GracefulQueueWork.php),继承Command,重写handle() - 开头调用
declare(ticks=1)(仅 CLI 有效) - 用
pcntl_signal(SIGTERM, fn() => $this->shouldQuit = true)设置退出标记 - 主循环里每轮都调用
pcntl_signal_dispatch(),否则信号永远不触发 - 检查
$this->shouldQuit时,只允许在当前任务结束后才退出,不能中断$job->handle()
示例关键片段:
declare(ticks=1);
$this->shouldQuit = false;
pcntl_signal(SIGTERM, function () {
$this->shouldQuit = true;
});
while (!$this->shouldQuit) {
$job = $this->popNextJob();
if ($job) {
$job->fire(); // 必须完整执行完
}
pcntl_signal_dispatch(); // 必须显式调用
}
Supervisor 下 SIGTERM 被吃掉怎么办
Supervisor 默认发送 SIGTERM 给主进程,但如果配置了 killasgroup=false 或 worker 启动时 fork 出子进程(比如某些日志库、DB 连接池),信号可能只终止了父进程,子进程继续跑,造成“假退出”。
- 务必在 Supervisor 配置中设
killasgroup=true和stopasgroup=true -
stopwaitsecs值必须 ≥ 最长单任务耗时 + 5 秒缓冲(比如任务最长 40 秒,就设 45) - 避免在
queue:work命令里加&或用nohup启动,这会让进程脱离 Supervisor 控制树 - 验证方式:
supervisorctl status显示 STOPPED 后,再ps aux | grep queue确认无残留 PID
Redis 缓存驱动失效导致 queue:restart 失效
php artisan queue:restart 本质是往缓存写一个 laravel_queue_restart_signal 键,所有 daemon worker 在每次循环末尾检查它。如果缓存驱动是 database 或 array,这个键只存在于当前进程内存或单个数据库连接里,跨进程不可见——于是部分 worker 收不到信号,还在跑旧代码。
- 生产环境必须用
redis或file缓存驱动(file要确保所有 worker 共享同一文件路径) - 检查
config/cache.php中'default' => env('CACHE_DRIVER', 'redis') - 运行
php artisan tinker后执行Cache::get('laravel_queue_restart_signal'),返回时间戳才算生效 - 别指望
database驱动能撑起优雅重启,它只适合开发或极轻量场景
真正难的不是写信号处理器,而是让信号从操作系统穿过 Supervisor、PHP 运行时、Laravel 生命周期,最终落到正在处理那个 job 的线程里——中间任何一环断开,就会变成“看起来停了,其实还在跑”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











