过去,你可能已经为服务器上需要调度的每个任务编写了一个 cron 配置条目。然而,这很快就会变得很麻烦,因为你的任务调度不再处于源代码管理中并且你必须 SSH 到服务器才能查看现有的 cron 条目或添加额外的条目。
Laravel 的命令调度器提供了一种全新的方法,用于管理服务器上的计划任务。该调度器允许你流畅且富有表现力地在 Laravel 应用内部定义你的命令调度。使用该调度器时,你的服务器上只需要一个 cron 条目。你的任务调度通常在应用的 routes/console.php 文件中定义。
您可以在您的应用程序的 routes/console.php 文件中定义所有定时任务。 为了开始,我们来看一个例子。 在这个例子中,我们将安排一个闭包每天午夜调用。 在该闭包中,我们将执行一个数据库查询以清空一个表:
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->daily();除了使用闭包进行调度,您还可以调度可调用对象。可调用对象是包含一个 __invoke 方法的简单 PHP 类:
Schedule::call(new DeleteRecentUsers)->daily();如果你倾向于将你的 routes/console.php 文件仅用于命令定义,你可以在应用程序的 bootstrap/app.php 文件中使用 withSchedule 方法来定义你的计划任务。这个方法接受一个闭包,该闭包会接收调度器的一个实例:
use Illuminate\Console\Scheduling\Schedule;
->withSchedule(function (Schedule $schedule) {
$schedule->call(new DeleteRecentUsers)->daily();
})如果您想查看您的计划任务概览以及它们下次运行的时间,您可以使用 schedule:list Artisan 命令:
php artisan schedule:list除了调度闭包,您还可以调度 Artisan 命令 和系统命令。例如,您可以使用 command 方法通过命令的名称或类来调度 Artisan 命令。
当使用命令的类名调度 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();如果你需要向闭包命令传递参数,你可以将它们提供给 schedule 方法:
Artisan::command('emails:send {user} {--force}', function ($user) {
// ...
})->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily();此 job 方法可用于调度一个 队列作业。该方法提供了一种便捷的方式来调度队列作业,而无需使用 call 方法来定义闭包以将作业加入队列:
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;
Schedule::job(new Heartbeat)->everyFiveMinutes();可选的第二个和第三个参数可以提供给 job 方法,用于指定应该使用的队列名称和队列连接来将作业排队:
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;
// Dispatch the job to the "heartbeats" queue on the "sqs" connection...
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();exec 方法可用于向操作系统发出命令:
use Illuminate\Support\Facades\Schedule;
Schedule::exec('node /home/forge/script.js')->daily();我们已经看到了一些关于如何配置任务以在指定间隔运行的示例。然而,还有更多可以分配给任务的任务调度频率:
这些方法可以与额外的约束结合以创建更精细调整的计划,这些计划只在每周的特定几天运行。例如,您可以安排一个命令在每周一运行:
use Illuminate\Support\Facades\Schedule;
// Run once per week on Monday at 1 PM...
Schedule::call(function () {
// ...
})->weekly()->mondays()->at('13:00');
// Run hourly from 8 AM to 5 PM on weekdays...
Schedule::command('foo')
->weekdays()
->hourly()
->timezone('America/Chicago')
->between('8:00', '17:00');附加的调度约束列表可在下方找到:
days 方法可用于将任务的执行限制在一周中的特定日期. 例如, 您可以安排一个命令在每周日和周三每小时运行一次:
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->hourly()
->days([0, 3]);或者,您可以使用 Illuminate\Console\Scheduling\Schedule 类中可用的常量来定义任务应该运行的日期:
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;
Facades\Schedule::command('emails:send')
->hourly()
->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);该 between 方法 可能 被 使用 来 限制 该 执行 的 一个 任务 基于 于 该 时间 的 一天:
Schedule::command('emails:send')
->hourly()
->between('7:00', '22:00');类似地,unlessBetween 方法可以用来在一段时间内排除某个任务的执行:
Schedule::command('emails:send')
->hourly()
->unlessBetween('23:00', '4:00');when 方法可用于根据给定真值测试的结果来限制任务的执行。换句话说,如果给定的闭包返回 true,只要没有其他限制条件阻止任务运行,该任务就会执行:
Schedule::command('emails:send')->daily()->when(function () {
return true;
});该 skip 方法可以看作是 when 的逆操作。如果 skip 方法返回 true,调度任务将不会被执行:
Schedule::command('emails:send')->daily()->skip(function () {
return true;
});当使用链式 when 方法时,计划命令只有在所有 when 条件都返回 true 时才会执行。
该 environments 方法可用于仅在给定环境中执行任务(根据 APP_ENV 环境变量 的定义):
Schedule::command('emails:send')
->daily()
->environments(['staging', 'production']);使用 timezone 方法,你可以指定计划任务的时间应在给定的时区中解释:
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->timezone('America/New_York')
->at('2:00')如果您反复为所有计划任务分配相同的时区, 您可以通过在您应用的 app 配置文件中定义一个 schedule_timezone 选项, 来指定应分配给所有计划的时区:
'timezone' => 'UTC',
'schedule_timezone' => 'America/Chicago',[!WARNING]
请注意,某些时区会使用夏令时。当夏令时变更发生时,您的定时任务可能会运行两次,甚至根本不运行。鉴于此,我们建议在可能的情况下避免时区调度。
默认情况下,即使任务的先前实例仍在运行,也会执行计划任务。为防止这种情况发生,您可以使用 withoutOverlapping 方法:
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->withoutOverlapping();在此示例中, emails:send Artisan 命令 将每分钟运行一次,前提是它尚未运行。 withoutOverlapping 方法在您有执行时间差异很大的任务时特别有用,这使您无法准确预测给定任务将花费多长时间。
如果需要,您可以指定在"不重叠"锁过期之前必须经过多少分钟。默认情况下,该锁将在 24 小时后过期:
Schedule::command('emails:send')->withoutOverlapping(10);在幕后,这个 withoutOverlapping 方法利用你的应用程序的 缓存 来获取锁。如果需要,你可以使用 schedule:clear-cache Artisan 命令来清除这些缓存锁。这通常只在任务因意外的服务器问题而卡住时才需要。
[!WARNING]
要使用此功能,您的应用程序必须使用database、memcached、dynamodb或redis缓存驱动程序作为您的应用程序的默认缓存驱动程序。此外,所有服务器都必须与同一个中央缓存服务器进行通信。
如果您的应用程序的调度器正在多台服务器上运行,您可以将一个计划任务限制为只在一台服务器上执行。 例如,假设您有一个计划任务,它在每个周五晚上生成一份新报告。 如果任务调度器正在三台工作服务器上运行,该计划任务将在所有三台服务器上运行,并生成三次报告。 这不好!
为了表明任务应该只在一个服务器上运行,请在定义计划任务时使用 onOneServer 方法。第一个获取到任务的服务器将为该作业获取一个原子锁,以防止其他服务器同时运行相同的任务:
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->fridays()
->at('17:00')
->onOneServer();您可以使用 useCache 方法来定制调度器使用的缓存存储,以获取单服务器任务所需的原子锁:
Schedule::useCache('database');有时您可能需要调度同一个作业,但使用不同的参数进行分派,同时仍然指示 Laravel 在单个服务器上运行该作业的每个排列。为实现此目的,您可以通过 name 方法为每个调度定义分配一个唯一的名称:
Schedule::job(new CheckUptime('https://laravel.com'))
->name('check_uptime:laravel.com')
->everyFiveMinutes()
->onOneServer();
Schedule::job(new CheckUptime('https://vapor.laravel.com'))
->name('check_uptime:vapor.laravel.com')
->everyFiveMinutes()
->onOneServer();类似地,如果计划任务闭包旨在在一台服务器上运行,则必须为其分配一个名称:
Schedule::call(fn () => User::resetApiRequestCount())
->name('reset-api-request-count')
->daily()
->onOneServer();By default, multiple tasks scheduled at the same time will execute sequentially based on the order they are defined in your schedule method. If you have long-running tasks, this may cause subsequent tasks to start much later than anticipated. If you would like to run tasks in the background so that they may all run simultaneously, you may use the runInBackground method:
use Illuminate\Support\Facades\Schedule;
Schedule::command('analytics:report')
->daily()
->runInBackground();[!WARNING]
runInBackground方法仅可用于通过command和exec方法调度任务时。
您的应用程序的计划任务在应用程序处于维护模式时将不会运行,因为我们不希望您的任务干扰您可能正在服务器上执行的任何未完成的维护。但是,如果您希望强制任务即使在维护模式下也能运行,您可以在定义任务时调用evenInMaintenanceMode 方法:
Schedule::command('emails:send')->evenInMaintenanceMode();当定义多个具有相似配置的计划任务时,您可以使用 Laravel 的任务分组功能,以避免为每个任务重复相同的设置。任务分组可以简化您的代码,并确保相关任务之间的一致性。
要创建一组计划任务,请调用所需的任务配置方法,然后是 group 方法。 group 方法接受一个闭包,该闭包负责定义共享指定配置的任务:
use Illuminate\Support\Facades\Schedule;
Schedule::daily()
->onOneServer()
->timezone('America/New_York')
->group(function () {
Schedule::command('emails:send --force');
Schedule::command('emails:prune');
});既然我们已经学会了如何定义计划任务,接下来讨论如何在服务器上实际运行它们。 schedule:run Artisan 命令将评估你所有的计划任务,并根据服务器的当前时间决定它们是否需要运行。
因此,在使用 Laravel 的调度器时,我们只需要在服务器上添加一个 cron 配置条目,它每分钟运行一次 schedule:run 命令。如果你不知道如何向服务器添加 cron 条目,可以考虑使用托管平台,例如 Laravel Cloud,它可以为你管理计划任务的执行:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1在大多数操作系统上,cron 任务被限制为每分钟最多运行一次。然而,Laravel 的调度器允许你以更频繁的间隔调度任务,甚至可以达到每秒一次:
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('recent_users')->delete();
})->everySecond();当你的应用程序中定义了子分钟任务时,schedule:run 命令将持续运行直到当前分钟结束,而不是立即退出。这使得命令能够在该分钟内调用所有必要的子分钟任务。
由于运行时间超出预期的子分钟任务可能会延迟后续子分钟任务的执行,建议所有子分钟任务都调度队列作业或后台命令来处理实际的任务处理:
use App\Jobs\DeleteRecentUsers;
Schedule::job(new DeleteRecentUsers)->everyTenSeconds();
Schedule::command('users:delete')->everyTenSeconds()->runInBackground();由于 schedule:run 命令在定义了亚分钟级任务时会运行一整分钟,因此在部署应用程序时,您有时可能需要中断该命令。否则,一个正在运行的 schedule:run 命令实例将继续使用您应用程序之前部署的代码,直到当前分钟结束。
为了中断正在进行的 schedule:run 调用,你可以将 schedule:interrupt 命令添加到你的应用程序的部署脚本中。此命令应在你的应用程序部署完成后调用:
php artisan schedule:interrupt通常,你不会将调度器 cron 条目添加到你的本地开发机器。相反,你可以使用 schedule:work Artisan 命令。此命令将在前台运行,并每分钟调用一次调度器,直到你终止该命令。当定义了亚分钟任务时,调度器将在一分钟内持续运行以处理这些任务:
php artisan schedule:workLaravel 调度器提供了几种便捷方法来处理调度任务生成的输出。首先,使用 sendOutputTo 方法,你可以将输出发送到一个文件以供后续检查:
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->sendOutputTo($filePath);如果您想将输出附加到给定文件,您可以使用 appendOutputTo 方法:
Schedule::command('emails:send')
->daily()
->appendOutputTo($filePath);使用 emailOutputTo 方法,您可以将输出通过电子邮件发送到您选择的电子邮件地址。在通过电子邮件发送任务输出之前,您应该配置 Laravel 的 电子邮件服务:
Schedule::command('report:generate')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('taylor@example.com');如果您只想在计划的 Artisan 或系统命令以非零退出代码终止时通过电子邮件发送输出,请使用 emailOutputOnFailure 方法:
Schedule::command('report:generate')
->daily()
->emailOutputOnFailure('taylor@example.com');[!WARNING]
该emailOutputTo,emailOutputOnFailure,sendOutputTo, 和appendOutputTo方法仅限于command和exec方法.
使用 before 和 after 方法,您可以指定在计划任务执行之前和之后要执行的代码:
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->before(function () {
// The task is about to execute...
})
->after(function () {
// The task has executed...
});onSuccess 和 onFailure 方法允许您指定在计划任务成功或失败时执行的代码。 失败表示计划的 Artisan 或系统命令以非零退出代码终止:
Schedule::command('emails:send')
->daily()
->onSuccess(function () {
// The task succeeded...
})
->onFailure(function () {
// The task failed...
});如果您的命令有输出,您可以在您的 after, onSuccess 或 onFailure 钩子中通过将 Illuminate\Support\Stringable 实例作为您的钩子闭包定义中的 $output 参数进行类型提示来访问它:
use Illuminate\Support\Stringable;
Schedule::command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// The task succeeded...
})
->onFailure(function (Stringable $output) {
// The task failed...
}); UsingpingBeforeandthenPing` methods, the scheduler can automatically ping a given URL before or after a task is executed. This method is useful for notifying an external service, such as Envoyer, that your scheduled task is beginning or has finished execution:
Schedule::command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);pingOnSuccess 和 pingOnFailure 方法可用于仅在任务成功或失败时对给定 URL 执行 ping 操作。失败表示计划的 Artisan 或系统命令以非零退出代码终止:
Schedule::command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);这些 pingBeforeIf,thenPingIf,pingOnSuccessIf,以及 pingOnFailureIf 方法可用于仅在给定条件为 true 时 ping 给定 URL:
Schedule::command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
Schedule::command('emails:send')
->daily()
->pingOnSuccessIf($condition, $successUrl)
->pingOnFailureIf($condition, $failureUrl);Laravel 分发各种事件在调度过程中。你可以定义监听器来监听以下任何事件: