最稳妥方案是用 schedule:run 配系统 Cron;Supervisor 运行 schedule:work 仅适用于毫秒级触发或无法使用 Cron 的特殊场景,否则会增加运维复杂度、资源开销及调度丢失风险。

直接用 schedule:run 配系统 Cron 是最稳妥的方案;Supervisor 启动 schedule:work 仅在必须毫秒级触发或无法操作 Cron 的场景下才值得上,否则反而增加运维复杂度和资源开销。
为什么不能只靠 Supervisor 运行 schedule:work
schedule:work 是 Laravel 9.2+ 引入的常驻调度进程,它轮询间隔默认 100ms,能绕过 Cron 的 1 分钟最小粒度限制。但它不是“万能替代”:
- 它不兼容
->at('02:00')这类精确到小时/分钟的语法——底层依赖的是 PHP 的microtime()+ 循环判断,实际触发时间可能漂移数秒 - 若服务器负载高、PHP 进程被 OOM kill 或超时退出,Supervisor 虽会重启它,但中间几秒的调度窗口就丢了,
daily()类任务可能漏跑 - 它会持续占用一个 PHP 进程(约 15–30MB 内存),而
schedule:run每次执行完即释放资源 - 日志里看不到“本次运行了哪些任务”,只有启动/重启记录,排障比 Cron +
schedule:run的 stdout 日志更难
supervisord 配置 schedule:work 的关键参数
如果真要上 schedule:work,以下几项不设对,基本等于白配:
-
command必须带--timezone=Asia/Shanghai(或你项目实际时区),否则它读的是系统默认时区,和config/app.php的'timezone'不一致,->dailyAt('03:00')就会错 8 小时 -
stopwaitsecs至少设为3600(1 小时),因为schedule:work收到 SIGTERM 后会等当前正在执行的任务结束才退出;若设太小(如默认 10 秒),它可能在跑备份命令时被粗暴杀掉 -
user必须和 Web 服务同用户(如www-data),否则可能因文件权限问题读不到storage/logs/或写不了缓存 -
stdout_logfile建议指向storage/logs/schedule-work.log,别用/var/log/下的全局路径——Docker 或非 root 环境里容易没权限
schedule:run + Cron 的最小可靠配置
这才是生产环境推荐的组合,99% 场景够用且稳定:
- Cron 条目必须用绝对路径:
* * * * * cd /var/www/myapp && /usr/bin/php artisan schedule:run >> /dev/null 2>&1,其中/usr/bin/php不能简写成php,避免 Cron 环境里 PATH 不含 PHP -
schedule:run输出建议重定向到日志:> /var/www/myapp/storage/logs/schedule-run.log 2>&1,方便查“为什么没触发” - 确认系统时区和 Laravel 时区一致:
php -r "echo date_default_timezone_get();"和config/app.php的'timezone'值必须相同,否则->hourly()可能在 13:59 执行而不是 14:00 - 任务逻辑里避免阻塞:比如发 1000 封邮件,别在
handle()里循环调用Mail::send(),应分发到队列,再由queue:work进程处理
Supervisor 和 queue:work 共存时的权限陷阱
很多人把 schedule:work 和 queue:work 都丢给 Supervisor 管,结果发现邮件只发一半——根本原因是两个进程用了不同用户:
-
schedule:work配置里user=www-data,但queue:work配置里写了user=root,导致队列任务生成的临时文件属主是 root,schedule:work进程读不到 - 所有 Supervisor 子进程(包括
schedule:work、queue:work、horizon)必须用同一用户启动,且该用户对storage/和bootstrap/cache/有读写权限 -
chown -R www-data:www-data storage/ bootstrap/cache/要在每次git pull后执行,否则新拉下来的文件属主是部署用户,Supervisor 启不动
真正需要毫秒级精度的调度极少,多数所谓“实时任务”其实容忍几十秒延迟;先用 schedule:run + Cron 跑稳,再根据监控数据(比如日志里频繁出现“missed at 02:00:03”)决定是否升级到 schedule:work,而不是一上来就堆复杂度。


















