Laravel 5.5 任务调度器默认仅支持单机基础调度,需手动配置系统 Cron 每分钟执行 schedule:run、切换 Redis 缓存启用 onOneServer() 防重复、显式声明时区、区分 command()/call()/exec() 执行模型,并自行补足分布式锁、失败重试与时区校准等能力。

直接说结论:Laravel 5.5 的任务调度器本身不支持分布式锁、无原生失败重试、不自动处理时区偏移,所有「进阶」能力都得靠手动补足——不是框架不能用,而是默认配置只够跑通 demo。
schedule:run 这条 cron 必须每分钟执行
服务器上那条 * * * * * cd /path && php artisan schedule:run 不是可选项,是唯一入口。Laravel 调度器本身不常驻内存,它靠这个命令每分钟拉起一次,再比对当前时间与你定义的 dailyAt('03:00') 或 cron('0 3 * * *') 是否匹配。
- 如果改成
*/5 * * * *(每 5 分钟),那everyMinute()和everyFiveMinutes()就会失效——因为调度器根本没机会检查 -
cd /path不能省,否则artisan可能找不到 autoload 或配置文件,报Class not found - 线上环境建议加日志重定向,比如
> /dev/null 2>&1改成> /var/log/laravel-schedule.log 2>&1,方便排查漏跑
避免多台服务器重复执行同一任务
Laravel 5.5 没有内置分布式协调机制,onOneServer() 是唯一可用的防护手段,但它依赖缓存驱动(默认是 file,线上必须切到 redis 或 database)。
- 若缓存驱动是
file,onOneServer()在多机环境下完全失效——每台机器都用自己的本地文件锁 - redis 缓存需确保所有服务器连的是同一个实例,且 key 前缀一致(检查
config/cache.php中的prefix) - 不要和
withoutOverlapping()混用:后者靠数据库或缓存存 running 标记,前者靠分布式锁,两者逻辑冲突,可能造成任务永久挂起
Artisan 命令 vs 闭包 vs exec:选哪个?
三者底层都走 Schedule 类的统一调度,但执行上下文和错误捕获能力差异很大:
-
$schedule->command('backup:run'):推荐用于封装完整业务逻辑的任务。好处是可复用、可测试、自带异常捕获(失败时抛出Exception,日志里能看到堆栈) -
$schedule->call(function () { DB::table('logs')->where('created_at', 'subDay())->delete(); }):仅限简单 SQL 或轻量操作。闭包无法被单元测试覆盖,且一旦报错(如连接超时),整个schedule:run进程会中断,后续任务全跳过 -
$schedule->exec('mysqldump ... > /backup/$(date +%F).sql'):适合调用外部二进制工具。注意 shell 环境变量($PATH)、工作目录、权限问题;失败不会抛 PHP 异常,只能靠返回码或日志判断
时区陷阱和 dailyAt() 的真实行为
dailyAt('03:00') 看似明确,但实际执行时间取决于三个时区设置的叠加结果:
- 服务器系统时区(
date命令输出) - PHP
date.timezone配置(php.ini或ini_set()) - Laravel 应用时区(
config/app.php中的'timezone' => 'UTC')
三者不一致时,dailyAt('03:00') 会按 Laravel 应用时区解析时间,再转成服务器本地时间去比对 cron 表达式。最稳妥的做法是显式声明:->dailyAt('03:00')->timezone('Asia/Shanghai'),且确保该时区字符串在服务器上已安装(timedatectl list-timezones | grep Shanghai)。
真正容易被忽略的是:Laravel 5.5 的 cron() 方法不校验表达式合法性,写错比如 cron('0 0 30 * *')(2 月没有 30 日)也不会报错,只是那天永远不触发——得靠日志或监控兜底。


















