必须重启队列进程并清除opcache才能加载新依赖:horizon用php artisan horizon:terminate,普通worker用kill或supervisorctl restart,并执行php artisan opcache:clear或重启php。

Horizon/Queue Worker 不会自动加载新依赖
改完 composer.json、跑完 composer install,队列进程里还是旧类——这不是 bug,是预期行为。PHP 进程启动后,已加载的类不会因磁盘文件变更而重新解析;Laravel Horizon 和普通 php artisan queue:work 都是长生命周期进程,不重启就不会读新代码。
必须显式重启 worker 才能生效
热更新 ≠ 无感更新。依赖变了,worker 必须重启才能加载新类、新函数、新配置。别信“重载”“刷新”这类词,PHP 没有运行时类热替换机制。
- Horizon:执行
php artisan horizon:terminate(它会向所有 Horizon 进程发 SIGTERM,worker 优雅退出后 supervisor 会拉起新进程) - 普通 queue worker:用
kill或supervisorctl restart干掉queue:work进程,确保新进程启动时加载的是当前vendor/状态 - 如果用
--daemon(已弃用但仍有项目在用),必须 kill 进程,--daemon模式下 evencomposer dump-autoload也不触发重载
opcache 会让问题更隐蔽
启用了 opcache(生产环境默认开启)时,即使重启了 worker,如果 opcache 没清空,仍可能执行旧字节码。这不是 Laravel 的问题,是 PHP 底层缓存行为。
- 检查是否启用:
opcache.enable和opcache.enable_cli(CLI 进程是否也缓存,影响artisan命令本身) - 重启 worker 前,建议先执行
php artisan opcache:clear(需 laravel/framework ≥ 9.25,或自定义命令调用opcache_reset()) - 若没装 opcache 清理包,直接
sudo systemctl reload php*-fpm(仅对 FPM 有效)或重启 PHP CLI opcache —— 但最稳的方式仍是:清 opcache → 重启 worker
CI/CD 部署时最容易漏掉的一步
很多部署脚本跑完 composer install --no-dev 就直接 reload nginx,完全忽略队列进程。结果 API 更新了,队列还在处理老逻辑,数据不一致悄无声息。
- 部署流程中必须包含 worker 终止步骤,且要等它真正退出(比如加
sleep 2或检查ps aux | grep queue:work) - Horizon 要确认
horizon:terminate返回成功,而不是被 supervisor 自动兜底立刻重启(那样可能新旧混跑) - 如果用 Kubernetes,注意 readiness probe 和 preStop hook 是否给够时间让 worker 处理完当前任务再退出
依赖更新不是改完就完事,worker 进程和 opcache 是两个独立的缓存层,都要手动捅一下。漏掉任何一个,就会在凌晨三点收到奇怪的队列失败告警。










