Skip to content

任务调度

介绍

过去,您可能为每个需要在服务器上调度的任务生成了一个 Cron 条目。然而,这很快就会变得麻烦,因为您的任务调度不再在源代码控制中,您必须 SSH 到服务器中以添加额外的 Cron 条目。

Laravel 的命令调度器允许您在 Laravel 内部流畅且富有表现力地定义命令调度。当使用调度器时,您的服务器上只需要一个 Cron 条目。您的任务调度在 app/Console/Kernel.php 文件的 schedule 方法中定义。为了帮助您入门,该方法中定义了一个简单的示例。

启动调度器

使用调度器时,您只需在服务器上添加以下 Cron 条目。如果您不知道如何在服务器上添加 Cron 条目,可以考虑使用 Laravel Forge 等服务来管理 Cron 条目:

php
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

这个 Cron 将每分钟调用 Laravel 命令调度器。当执行 schedule:run 命令时,Laravel 将评估您的计划任务并运行到期的任务。

定义调度

您可以在 App\Console\Kernel 类的 schedule 方法中定义所有计划任务。首先,让我们看一个调度任务的示例。在此示例中,我们将计划每天午夜调用一个 Closure。在 Closure 中,我们将执行一个数据库查询以清空一个表:

php
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\DB;

class Kernel extends ConsoleKernel
{
    /**
     * 应用程序提供的 Artisan 命令。
     *
     * @var array
     */
    protected $commands = [
        //
    ];

    /**
     * 定义应用程序的命令调度。
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}

除了使用 Closures 调度,您还可以使用 可调用对象。可调用对象是包含 __invoke 方法的简单 PHP 类:

php
$schedule->call(new DeleteRecentUsers)->daily();

调度 Artisan 命令

除了调度 Closure 调用,您还可以调度 Artisan 命令 和操作系统命令。例如,您可以使用 command 方法通过命令的名称或类来调度 Artisan 命令:

php
$schedule->command('emails:send Taylor --force')->daily();

$schedule->command(EmailsCommand::class, ['Taylor', '--force'])->daily();

调度队列任务

可以使用 job 方法调度 队列任务。此方法提供了一种方便的方法来调度任务,而无需使用 call 方法手动创建队列任务的 Closures:

php
$schedule->job(new Heartbeat)->everyFiveMinutes();

// 将任务派发到 "heartbeats" 队列...
$schedule->job(new Heartbeat, 'heartbeats')->everyFiveMinutes();

调度 Shell 命令

可以使用 exec 方法向操作系统发出命令:

php
$schedule->exec('node /home/forge/script.js')->daily();

调度频率选项

您可以为任务分配多种调度:

方法描述
->cron('* * * * *');按自定义 Cron 调度运行任务
->everyMinute();每分钟运行任务
->everyTwoMinutes();每两分钟运行任务
->everyThreeMinutes();每三分钟运行任务
->everyFourMinutes();每四分钟运行任务
->everyFiveMinutes();每五分钟运行任务
->everyTenMinutes();每十分钟运行任务
->everyFifteenMinutes();每十五分钟运行任务
->everyThirtyMinutes();每三十分钟运行任务
->hourly();每小时运行任务
->hourlyAt(17);每小时在 17 分钟时运行任务
->everyTwoHours();每两小时运行任务
->everyThreeHours();每三小时运行任务
->everyFourHours();每四小时运行任务
->everySixHours();每六小时运行任务
->daily();每天午夜运行任务
->dailyAt('13:00');每天 13:00 运行任务
->twiceDaily(1, 13);每天 1:00 和 13:00 运行任务
->weekly();每周日 00:00 运行任务
->weeklyOn(1, '8:00');每周一 8:00 运行任务
->monthly();每月第一天 00:00 运行任务
->monthlyOn(4, '15:00');每月 4 日 15:00 运行任务
->lastDayOfMonth('15:00');每月最后一天 15:00 运行任务
->quarterly();每季度第一天 00:00 运行任务
->yearly();每年第一天 00:00 运行任务
->timezone('America/New_York');设置时区

这些方法可以与其他约束结合使用,以创建更精细的调度,仅在特定的星期几运行。例如,要调度一个命令在每周一运行:

php
// 每周一下午 1 点运行一次...
$schedule->call(function () {
    //
})->weekly()->mondays()->at('13:00');

// 在工作日的上午 8 点到下午 5 点之间每小时运行一次...
$schedule->command('foo')
          ->weekdays()
          ->hourly()
          ->timezone('America/Chicago')
          ->between('8:00', '17:00');

以下是其他调度约束的列表:

方法描述
->weekdays();限制任务在工作日运行
->weekends();限制任务在周末运行
->sundays();限制任务在周日运行
->mondays();限制任务在周一运行
->tuesdays();限制任务在周二运行
->wednesdays();限制任务在周三运行
->thursdays();限制任务在周四运行
->fridays();限制任务在周五运行
->saturdays();限制任务在周六运行
->days(array|mixed);限制任务在特定的天运行
->between($start, $end);限制任务在开始和结束时间之间运行
->when(Closure);基于真值测试限制任务
->environments($env);限制任务在特定环境中运行

天约束

days 方法可用于限制任务在特定的星期几运行。例如,您可以调度一个命令在周日和周三每小时运行:

php
$schedule->command('reminders:send')
                ->hourly()
                ->days([0, 3]);

时间约束

between 方法可用于根据一天中的时间限制任务的执行:

php
$schedule->command('reminders:send')
                    ->hourly()
                    ->between('7:00', '22:00');

同样,unlessBetween 方法可用于排除任务在一段时间内的执行:

php
$schedule->command('reminders:send')
                    ->hourly()
                    ->unlessBetween('23:00', '4:00');

真值测试约束

when 方法可用于基于给定的真值测试限制任务的执行。换句话说,如果给定的 Closure 返回 true,任务将执行,只要没有其他约束条件阻止任务运行:

php
$schedule->command('emails:send')->daily()->when(function () {
    return true;
});

skip 方法可以看作是 when 的反义词。如果 skip 方法返回 true,则计划任务将不会执行:

php
$schedule->command('emails:send')->daily()->skip(function () {
    return true;
});

使用链式 when 方法时,计划的命令只有在所有 when 条件返回 true 时才会执行。

环境约束

environments 方法可用于仅在给定环境中执行任务:

php
$schedule->command('emails:send')
            ->daily()
            ->environments(['staging', 'production']);

时区

使用 timezone 方法,您可以指定计划任务的时间应在给定时区内解释:

php
$schedule->command('report:generate')
         ->timezone('America/New_York')
         ->at('02:00')

如果您为所有计划任务分配相同的时区,您可能希望在 app/Console/Kernel.php 文件中定义一个 scheduleTimezone 方法。此方法应返回应分配给所有计划任务的默认时区:

php
/**
 * 获取应默认用于计划事件的时区。
 *
 * @return \DateTimeZone|string|null
 */
protected function scheduleTimezone()
{
    return 'America/Chicago';
}
exclamation

请记住,某些时区使用夏令时。当夏令时变化发生时,您的计划任务可能会运行两次甚至不运行。因此,我们建议尽可能避免使用时区调度。

防止任务重叠

默认情况下,即使上一个任务实例仍在运行,计划任务也会运行。要防止这种情况,您可以使用 withoutOverlapping 方法:

php
$schedule->command('emails:send')->withoutOverlapping();

在此示例中,如果 emails:send Artisan 命令 尚未运行,它将每分钟运行一次。withoutOverlapping 方法特别有用,如果您有执行时间变化很大的任务,防止您无法准确预测给定任务需要多长时间。

如果需要,您可以指定在 "without overlapping" 锁过期之前必须经过多少分钟。默认情况下,锁将在 24 小时后过期:

php
$schedule->command('emails:send')->withoutOverlapping(10);

在一台服务器上运行任务

exclamation

要使用此功能,您的应用程序必须使用 databasememcachedredis 缓存驱动作为应用程序的默认缓存驱动。此外,所有服务器必须与同一个中央缓存服务器通信。

如果您的应用程序在多台服务器上运行,您可以限制计划任务仅在一台服务器上执行。例如,假设您有一个计划任务,每周五晚上生成一个新报告。如果任务调度器在三台工作服务器上运行,计划任务将在所有三台服务器上运行并生成三次报告。这不好!

要指示任务仅在一台服务器上运行,请在定义计划任务时使用 onOneServer 方法。第一个获取任务的服务器将获得任务的原子锁,以防止其他服务器同时运行相同的任务:

php
$schedule->command('report:generate')
                ->fridays()
                ->at('17:00')
                ->onOneServer();

后台任务

默认情况下,同时计划的多个命令将按顺序执行。如果您有长时间运行的命令,这可能会导致后续命令的启动时间比预期的要晚得多。如果您希望命令在后台运行,以便它们可以同时运行,可以使用 runInBackground 方法:

php
$schedule->command('analytics:report')
         ->daily()
         ->runInBackground();
exclamation

runInBackground 方法只能在通过 commandexec 方法调度任务时使用。

维护模式

Laravel 的计划任务在 Laravel 处于 维护模式 时不会运行,因为我们不希望您的任务干扰您可能正在服务器上执行的任何未完成的维护。然而,如果您希望即使在维护模式下也强制运行任务,可以使用 evenInMaintenanceMode 方法:

php
$schedule->command('emails:send')->evenInMaintenanceMode();

任务输出

Laravel 调度器提供了几种方便的方法来处理计划任务生成的输出。首先,使用 sendOutputTo 方法,您可以将输出发送到文件以供以后检查:

php
$schedule->command('emails:send')
         ->daily()
         ->sendOutputTo($filePath);

如果您希望将输出附加到给定文件,可以使用 appendOutputTo 方法:

php
$schedule->command('emails:send')
         ->daily()
         ->appendOutputTo($filePath);

使用 emailOutputTo 方法,您可以将输出通过电子邮件发送到您选择的电子邮件地址。在通过电子邮件发送任务的输出之前,您应该配置 Laravel 的 电子邮件服务

php
$schedule->command('foo')
         ->daily()
         ->sendOutputTo($filePath)
         ->emailOutputTo('foo@example.com');

如果您只希望在命令失败时通过电子邮件发送输出,请使用 emailOutputOnFailure 方法:

php
$schedule->command('foo')
         ->daily()
         ->emailOutputOnFailure('foo@example.com');
exclamation

emailOutputToemailOutputOnFailuresendOutputToappendOutputTo 方法仅适用于 commandexec 方法。

任务钩子

使用 beforeafter 方法,您可以指定在计划任务完成之前和之后执行的代码:

php
$schedule->command('emails:send')
         ->daily()
         ->before(function () {
             // 任务即将开始...
         })
         ->after(function () {
             // 任务完成...
         });

onSuccessonFailure 方法允许您指定在计划任务成功或失败时执行的代码:

php
$schedule->command('emails:send')
         ->daily()
         ->onSuccess(function () {
             // 任务成功...
         })
         ->onFailure(function () {
             // 任务失败...
         });

Pinging URLs

使用 pingBeforethenPing 方法,调度器可以在任务完成之前或之后自动 ping 给定的 URL。此方法对于通知外部服务(如 Laravel Envoyer)您的计划任务正在开始或已完成执行非常有用:

php
$schedule->command('emails:send')
         ->daily()
         ->pingBefore($url)
         ->thenPing($url);

pingBeforeIfthenPingIf 方法可用于仅在给定条件为 true 时 ping 给定的 URL:

php
$schedule->command('emails:send')
         ->daily()
         ->pingBeforeIf($condition, $url)
         ->thenPingIf($condition, $url);

pingOnSuccesspingOnFailure 方法可用于仅在任务成功或失败时 ping 给定的 URL:

php
$schedule->command('emails:send')
         ->daily()
         ->pingOnSuccess($successUrl)
         ->pingOnFailure($failureUrl);

所有 ping 方法都需要 Guzzle HTTP 库。您可以使用 Composer 包管理器将 Guzzle 添加到您的项目中:

php
composer require guzzlehttp/guzzle