laravel计划任务需服务器cron调用schedule:run,配置时须设app_env=production、正确设置config/app.php的timezone、用cd切换目录执行命令,并根据需求选command()或call()。

定时任务没跑起来?先确认 schedule:run 是否被正确调用
Laravel 的计划任务本身不自动执行,它只生成一个调度逻辑,真正触发靠的是服务器的 cron。很多人配完 App\Console\Kernel::schedule() 就以为万事大吉,结果任务从不运行——根本原因是没在系统级 cron 里加那行固定调用。
实操建议:
- 在服务器上执行
crontab -e,添加这一行(路径按你项目实际调整):* * * * * cd /var/www/myapp && php artisan schedule:run >> /dev/null 2>&1
- 别用
php /var/www/myapp/artisan schedule:run这种绝对路径写法,cd切目录才能保证加载正确的.env和 autoload - 检查
APP_ENV是不是production;开发环境默认不启用调度(schedule:run会静默退出) - 临时验证是否通:手动运行
php artisan schedule:run,看输出有没有 “Running scheduled command” 或报错
$schedule->command() 和 $schedule->call() 怎么选
前者走命令生命周期(handle()、中间件、异常处理完整链路),后者直接调函数,绕过命令容器。选错会导致依赖注入失败、日志不进 laravel.log、或事务不生效。
常见错误现象:
- 用
call()调用了需要DB::transaction()的方法,但事务没回滚——因为没经过命令层的异常捕获 - 在
call()里用了$this->info(),终端没输出——call()不走Command的输出通道
使用场景建议:
- 要发邮件、写日志、用队列、需完整 Laravel 上下文 → 用
command('app:sync-orders'),并确保该命令继承Illuminate\Console\Command - 只是简单更新缓存、打个标记、调个 API → 可用
call(function () { cache()->put('last_run', now()); }),轻量且无开销
时区不对、时间漂移?重点看这三个配置
Laravel 调度器默认用服务器本地时区,但 APP_TIMEZONE 和 date_default_timezone_set() 都不影响它——它只认 config/app.php 里的 'timezone' 键,且必须是 IANA 格式(如 'Asia/Shanghai',不能写 'GMT+8')。
容易踩的坑:
- 服务器时区是 UTC,但你在
schedule()里写->dailyAt('02:00'),结果任务在 UTC 时间 02:00(即北京时间 10:00)跑 —— 因为没设timezone - 改了
config/app.php的timezone,但忘了清配置缓存:php artisan config:clear - 在
schedule()闭包里用now(),它返回的是服务器时区时间,和调度器解析 cron 表达式用的时区可能不一致 → 统一用CronExpression::factory('0 2 * * *')->getPreviousRunDate()类方式校验
任务卡住或并发冲突?withoutOverlapping() 不是万能解
这个方法靠在缓存里写锁键(scheduling:xxx)来防重入,但它只对同一台机器有效。如果你用多台 Web 服务器跑同一个 Laravel 应用,或者用 Redis 做缓存但没配置共享连接,withoutOverlapping() 就会失效。
性能与兼容性影响:
- 锁键默认 TTL 是 24 小时,如果任务真卡死,得等一天才自动释放 → 可显式传参:
withoutOverlapping(3600)(单位秒) - 如果用文件缓存,多机部署下完全无效;用 Redis 时,确保所有机器连的是同一个 Redis 实例
- 更稳妥的做法是任务内部做幂等判断:比如查数据库里是否有今天已处理的记录,有则直接 return
复杂点在于:调度器本身不提供分布式锁原语,也无超时 kill 机制。一旦某个 command 在执行中挂起(比如 cURL 死等),withoutOverlapping() 只能拦住下一个周期,拦不住当前进程。这种地方得靠系统级监控补位。










