跳转到主要内容

什么是任务调度

在传统方式下,你需要为每个定时任务手写 cron 条目。 这样做的问题是调度定义脱离源码,无法进行版本管理,每次调整都要 SSH 登录服务器。 Laravel 的调度器允许你在应用内部以流畅的语法定义调度。 只需要在服务器上加一行 cron 条目,其他调度定义都随代码一起进入版本管理。 标准写法是把调度定义写在 routes/console.php 里。
<?php

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
使用 schedule:list Artisan 命令可以查看已定义任务的列表以及下一次执行时间。
php artisan schedule:list

调度器的执行流程

定义调度

调度定义写在 routes/console.php。 也可以在 bootstrap/app.phpwithSchedule 方法中定义。
// bootstrap/app.php
use Illuminate\Console\Scheduling\Schedule;

->withSchedule(function (Schedule $schedule) {
    $schedule->call(new DeleteRecentUsers)->daily();
})

可调度的任务类型

调度 Artisan 命令

使用 command 方法调度 Artisan 命令。可以传入命令名或类名。
use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;

// 通过命令名
Schedule::command('emails:send Taylor --force')->daily();

// 通过类名(参数以数组传入)
Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
用闭包定义的 Artisan 命令可以在定义之后直接链式添加调度方法。
Artisan::command('delete:recent-users', function () {
    DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();

调度队列任务

使用 job 方法调度队列任务。 无需闭包即可让任务入队,非常方便。
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

Schedule::job(new Heartbeat)->everyFiveMinutes();
也可以指定队列名与连接。
// 派发到 "heartbeats" 队列,使用 "sqs" 连接
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();

调度 shell 命令

exec 方法用来执行操作系统命令。
use Illuminate\Support\Facades\Schedule;

Schedule::exec('node /home/forge/script.js')->daily();

调度闭包

call 方法可以调度任意 PHP 闭包。
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
也可以传入带 __invoke 方法的 Invocable 对象。
Schedule::call(new DeleteRecentUsers)->daily();

设置执行频率

常用频率方法

以下是常用的频率方法。
方法说明
->everySecond()每秒执行
->everyMinute()每分钟执行
->everyFiveMinutes()每 5 分钟执行
->everyFifteenMinutes()每 15 分钟执行
->everyThirtyMinutes()每 30 分钟执行
->hourly()每小时执行
->hourlyAt(17)每小时的第 17 分钟执行
->daily()每天 0 点执行
->dailyAt('13:00')每天 13 点执行
->twiceDaily(1, 13)每天 1 点和 13 点执行
->weekly()每周日 0 点执行
->weeklyOn(1, '8:00')每周一 8 点执行
->monthly()每月 1 日 0 点执行
->monthlyOn(4, '15:00')每月 4 日 15 点执行
->quarterly()每季度首日 0 点执行
->yearly()每年 1 月 1 日 0 点执行
->timezone('Asia/Shanghai')指定时区

直接使用 cron 表达式

也可以通过 cron 方法直接使用 cron 表达式。
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->cron('0 9 * * *'); // 每天 9 点执行

频率与星期组合

可以把频率方法与星期约束组合出更精细的调度。
use Illuminate\Support\Facades\Schedule;

// 每周一 13 点执行
Schedule::call(function () {
    // ...
})->weekly()->mondays()->at('13:00');

// 工作日 8 点到 17 点每小时执行
Schedule::command('foo')
    ->weekdays()
    ->hourly()
    ->timezone('Asia/Shanghai')
    ->between('8:00', '17:00');

设置时区

timezone 方法可以为单个任务指定时区。
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->timezone('Asia/Shanghai')
    ->at('9:00');
想为所有任务设置公共时区,可以配置 config/app.phpschedule_timezone
// config/app.php
'schedule_timezone' => 'Asia/Shanghai',
如果时区包含夏令时,切换时任务可能被执行两次或完全不执行。 尽量使用 UTC。

条件约束

when / skip

when 只在闭包返回 true 时执行任务。 skip 相反,返回 true 时跳过。
use Illuminate\Support\Facades\Schedule;

// 条件为 true 时执行
Schedule::command('emails:send')->daily()->when(function () {
    return true;
});

// 条件为 true 时跳过
Schedule::command('emails:send')->daily()->skip(function () {
    return true;
});

environments

environments 方法限制任务只在特定环境执行。
Schedule::command('emails:send')
    ->daily()
    ->environments(['staging', 'production']);

时间段约束

between / unlessBetween 可以限制执行的时间段。
// 只在 7 点到 22 点之间执行
Schedule::command('emails:send')
    ->hourly()
    ->between('7:00', '22:00');

// 23 点到 4 点之间不执行
Schedule::command('emails:send')
    ->hourly()
    ->unlessBetween('23:00', '4:00');

星期约束

方法说明
->weekdays()仅工作日
->weekends()仅周末
->mondays()->sundays()特定星期
->days([0, 3])同时指定周日(0)和周三(3)等多天
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;

Facades\Schedule::command('emails:send')
    ->hourly()
    ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);

防止重叠

默认情况下,即便上一次任务尚未结束,下一次执行也会照常开始。 使用 withoutOverlapping 会等上一次执行完再开始下一次。
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->withoutOverlapping();
可以指定锁的过期时间(分钟),默认 24 小时。
// 10 分钟后锁自动过期
Schedule::command('emails:send')->withoutOverlapping(10);
withoutOverlapping 使用应用缓存来管理锁。 如果任务因异常卡死,可以用 schedule:clear-cache 清除锁。
php artisan schedule:clear-cache

多服务器执行控制

多台服务器同时运行调度器时,onOneServer 可以让任务只在其中一台上执行。
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->fridays()
    ->at('17:00')
    ->onOneServer();
使用此功能需要将应用默认缓存驱动设为 databasememcacheddynamodbredis,且所有服务器连接到同一个缓存服务。

onOneServer() 的分布式执行流程

任务分组

如果多个任务共用同样的设置,可以用 group 方法一次配置。
use Illuminate\Support\Facades\Schedule;

Schedule::daily()
    ->onOneServer()
    ->timezone('Asia/Shanghai')
    ->group(function () {
        Schedule::command('emails:send --force');
        Schedule::command('emails:prune');
    });

后台执行

同一时刻的多个任务默认按定义顺序串行执行。 长任务会拖慢后续任务。 runInBackground 会让任务在后台并行执行。
use Illuminate\Support\Facades\Schedule;

Schedule::command('analytics:report')
    ->daily()
    ->runInBackground();
runInBackground 只能用于 commandexec 方法。

维护模式

应用进入维护模式时,调度任务默认不会执行。 若要强制运行,可使用 evenInMaintenanceMode
Schedule::command('emails:send')->evenInMaintenanceMode();

维护模式下的处理流程

暂停调度器

无需改代码即可暂停调度器。
# 暂停
php artisan schedule:pause

# 恢复
php artisan schedule:continue
即使暂停,也可以让部分任务继续运行,通过 evenWhenPaused。 适用于健康检查、监控等在维护期仍需运行的任务。
Schedule::command('emails:send')->evenWhenPaused();

处理输出

输出到文件

sendOutputTo 可以把任务输出写入文件。
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->sendOutputTo(storage_path('logs/emails-send.log'));
appendOutputTo 则会追加到已有文件末尾。
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo(storage_path('logs/emails-send.log'));

输出到邮件

emailOutputTo 可以把任务输出以邮件发送。 需要事先完成 Laravel 的邮件配置
Schedule::command('report:generate')
    ->daily()
    ->sendOutputTo($filePath)
    ->emailOutputTo('[email protected]');
只在失败时发送邮件,使用 emailOutputOnFailure
Schedule::command('report:generate')
    ->daily()
    ->emailOutputOnFailure('[email protected]');

任务钩子

before / after 用于在任务前后插入逻辑。
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->before(function () {
        // 执行前
    })
    ->after(function () {
        // 执行后
    });
onSuccess / onFailure 用于成功/失败时的钩子。
use Illuminate\Support\Stringable;

Schedule::command('emails:send')
    ->daily()
    ->onSuccess(function (Stringable $output) {
        // 成功时处理
    })
    ->onFailure(function (Stringable $output) {
        // 失败时处理
    });

服务器部署

1

添加 cron 条目

只需在服务器 crontab 中加入下面一行,Laravel 调度器就会每分钟运行。
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
使用 crontab -e 命令编辑。
2

确认调度器运行情况

查看已定义任务及下一次执行时间。
php artisan schedule:list
使用 Laravel Cloud 时,无需配置 cron 即可管理调度任务。

本地开发运行

本地开发时可以不使用 cron,而是通过 schedule:work 命令让调度器常驻。
php artisan schedule:work
该命令在前台运行,每分钟触发一次调度,直到你 Ctrl+C 停止。

亚分钟级调度(间隔小于 1 分钟)

常规 cron 的最小单位是分钟,但 Laravel 支持秒级调度。
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->everySecond();
如果定义了亚分钟任务,schedule:run 会持续运行到本分钟结束,处理所有亚分钟任务。
建议把亚分钟任务委派给队列任务或后台命令。 任务本身耗时较长时,会延迟后续亚分钟任务的执行。
若要在部署时中断正在运行的 schedule:run,可以在部署脚本中加入:
php artisan schedule:interrupt

常用命令速查

# 显示任务列表
php artisan schedule:list

# 手动执行调度器(cron 会调用的命令)
php artisan schedule:run

# 本地开发用常驻运行
php artisan schedule:work

# 暂停调度器
php artisan schedule:pause

# 恢复调度器
php artisan schedule:continue

# 清理重叠锁
php artisan schedule:clear-cache

# 中断亚分钟任务的执行(部署时使用)
php artisan schedule:interrupt
最后修改于 2026年7月13日