定时任务未执行需先确认系统Cron是否正确配置php artisan schedule:run,检查crontab -e中是否含绝对路径的执行命令,并用schedule:list和schedule:work调试验证。

定时任务没执行?先确认 schedule:run 是否被正确调用
Laravel 的任务调度不是开箱即用的「后台守护进程」,它依赖外部 Cron 每分钟触发一次 php artisan schedule:run。如果任务从不执行,90% 是因为服务器 Cron 没配,或配错了路径。
- 检查系统 Cron:运行
crontab -e,确保有类似这行(注意替换为你的实际 PHP 路径和项目路径):* * * * * /usr/bin/php /var/www/your-app/artisan schedule:run >> /dev/null 2>&1
- 不要用
php别名——生产环境常无此 alias,必须写绝对路径,如/usr/bin/php或/opt/plesk/php/8.1/bin/php - 测试是否能手动触发:在项目根目录下执行
php artisan schedule:run,观察输出是否列出“Running scheduled command”及对应命令
$schedule->command() 和 $schedule->call() 的本质区别
两者底层都走 Laravel 的调度流程,但执行时机与上下文完全不同:前者是启动新进程跑 Artisan 命令,后者是在当前 PHP 进程内直接调用闭包或方法。
-
$schedule->command('backup:database'):等价于 shell 执行php artisan backup:database,有完整命令生命周期、异常捕获、日志隔离;适合耗时长、需独立内存空间的任务 -
$schedule->call(function () { \Log::info('ping'); }):直接在schedule:run进程中执行,无命令开销,但若闭包里抛出未捕获异常,整个调度会中断,且无法利用 Artisan 命令的--no-interaction等参数 - 注意:闭包不能访问 request/session —— 因为没有 HTTP 上下文;也不能依赖
$this->app中某些只在 Web 请求中初始化的绑定
如何让定时任务支持多服务器部署而不重复执行
当应用部署在多台机器(如负载均衡后),默认所有节点都会运行 schedule:run,导致任务重复。Laravel 不内置分布式锁,需自行控制。
- 最轻量方案:用缓存原子操作打标,例如 Redis:
$schedule->command('notify:daily')->everyMinute()->withoutOverlapping();,其中withoutOverlapping()默认基于文件缓存(storage/framework/cache/data/...),仅适用于单机;要跨机,得配合cache()->store('redis')->lock(...)自定义实现 - 更可靠做法:把关键任务入口统一收口到一个「调度中心」服务(比如某台固定机器),其他节点 Cron 注释掉
schedule:run,只保留健康检查 - 避免踩坑:别用数据库字段(如
is_running)做锁——高并发下易出现竞态,且没自动过期机制;Redis 锁必须设 TTL(如 300 秒),防止死锁
本地开发调试时,schedule:list 和 schedule:work 怎么用
开发阶段不需要配系统 Cron,Laravel 提供两个实用命令辅助验证逻辑。
-
php artisan schedule:list:列出所有注册的调度任务及其下次预计运行时间(基于当前时间 + 频率计算),注意它不检查命令是否存在,只解析app/Console/Kernel.php中的schedule()方法 -
php artisan schedule:work:Laravel 5.8+ 新增,会持续监听并按需执行任务(类似 Supervisor 启动一个长进程),适合开发时观察实时行为;但它不模拟真实 Cron 的一分钟粒度,而是每秒轮询,且退出后任务就停 —— 切勿用于生产 - 常见误操作:改完
Kernel.php后忘记清配置缓存,导致schedule:list显示旧任务 —— 运行php artisan config:clear即可
任务调度真正的复杂点不在语法,而在于执行环境的一致性:PHP 版本、时区设置(date_default_timezone_set() 影响 ->dailyAt())、队列连接配置、以及最关键的——你永远不知道哪台机器上的 Cron 突然被运维删了。










