任务调度

Introduction

过去,您可能已经为需要在服务器上安排的每个任务编写了一个 cron 配置条目。但是,这很快就会变得很痛苦,因为您的任务计划不再受源代码控制,您必须通过 SSH 连接到您的服务器才能查看现有的 cron 条目或添加其他条目。

Laravel 的命令调度器提供了一种全新的方法来管理服务器上的计划任务。调度程序允许您在 Laravel 应用程序本身中流畅而富有表现力地定义命令调度。使用调度程序时,服务器上只需要一个 cron 条目。您的任务计划在app/Console/Kernel.php 文件的schedule 方法。为了帮助您入门,在该方法中定义了一个简单的示例。

定义时间表

您可以在schedule 你的应用程序的方法App\Console\Kernel 班级。首先,让我们看一个例子。在这个例子中,我们将安排在每天午夜调用一个闭包。在闭包中,我们将执行一个数据库查询来清除一个表:

<?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
{
    /**
     * Define the application's command schedule.
     */
    protected function schedule(Schedule $schedule): void
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}

除了使用闭包进行调度之外,您还可以调度可调用对象.可调用对象是简单的 PHP 类,其中包含__invoke 方法:

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

如果您想查看计划任务的概览以及计划的下一次运行时间,您可以使用schedule:list 工匠命令:

php artisan schedule:list

调度 Artisan 命令

除了安排关闭,您还可以安排工匠命令 和系统命令。例如,您可以使用command 使用命令的名称或类来调度 Artisan 命令的方法。

当使用命令的类名调度 Artisan 命令时,您可以传递一组额外的命令行参数,这些参数应该在命令被调用时提供给命令:

use App\Console\Commands\SendEmailsCommand;

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

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

安排排队的作业

job 方法可用于安排排队的工作.此方法提供了一种无需使用call 定义闭包以对作业进行排队的方法:

use App\Jobs\Heartbeat;

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

可选的第二个和第三个参数可以提供给job 方法指定用于排队作业的队列名称和队列连接:

use App\Jobs\Heartbeat;

// Dispatch the job to the "heartbeats" queue on the "sqs" connection...
$schedule->job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();

调度 Shell 命令

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

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

计划频率选项

我们已经看到了几个示例,说明您可以如何将任务配置为以指定的时间间隔运行。但是,您可以为任务分配更多的任务计划频率:

Method Description
->cron('* * * * *'); 在自定义 cron 计划上运行任务
->everyMinute(); 每分钟运行一次任务
->everyTwoMinutes(); 每两分钟运行一次任务
->everyThreeMinutes(); 每三分钟运行一次任务
->everyFourMinutes(); 每四分钟运行一次任务
->everyFiveMinutes(); 每五分钟运行一次任务
->everyTenMinutes(); 每十分钟运行一次任务
->everyFifteenMinutes(); 每十五分钟运行一次任务
->everyThirtyMinutes(); 每三十分钟运行一次任务
->hourly(); 每小时运行一次任务
->hourlyAt(17); 每小时 17 分钟运行一次任务
->everyOddHour(); 每隔几个小时运行一次任务
->everyTwoHours(); 每两个小时运行一次任务
->everyThreeHours(); 每三个小时运行一次任务
->everyFourHours(); 每四个小时运行一次任务
->everySixHours(); 每六个小时运行一次任务
->daily(); 每天午夜运行任务
->dailyAt('13:00'); 每天13:00运行任务
->twiceDaily(1, 13); 每天 1:00 和 13:00 运行任务
->twiceDailyAt(1, 13, 15); 每天 1:15 和 13:15 运行任务
->weekly(); 每周日 00:00 运行任务
->weeklyOn(1, '8:00'); 每周一 8:00 运行任务
->monthly(); 在每个月的第一天 00:00 运行任务
->monthlyOn(4, '15:00'); 每月4号15:00运行任务
->twiceMonthly(1, 16, '13:00'); 每月1号和16号13:00运行任务
->lastDayOfMonth('15:00'); 在每月最后一天的 15:00 运行任务
->quarterly(); 在每个季度的第一天 00:00 运行任务
->quarterlyOn(4, '14:00'); 每季度4号14:00运行任务
->yearly(); 在每年的第一天 00:00 运行任务
->yearlyOn(6, 1, '17:00'); 每年6月1日17:00运行任务
->timezone('America/New_York'); 设置任务的时区

这些方法可以与额外的约束相结合,以创建仅在一周中的特定几天运行的更精细调整的时间表。例如,您可以安排一个命令每周在星期一运行:

// 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');

额外的时间表限制列表可以在下面找到:

Method Description
->weekdays(); 将任务限制在工作日
->weekends(); 将任务限制在周末
->sundays(); 将任务限制在周日
->mondays(); 将任务限制在星期一
->tuesdays(); 将任务限制在星期二
->wednesdays(); 将任务限制在星期三
->thursdays(); 将任务限制在星期四
->fridays(); 将任务限制在星期五
->saturdays(); 将任务限制在星期六
->days(array|mixed); 将任务限制在特定日期
->between($startTime, $endTime); 限制任务在开始时间和结束时间之间运行
->unlessBetween($startTime, $endTime); 限制任务不在开始时间和结束时间之间运行
->when(Closure); 根据真值测试限制任务
->environments($env); 将任务限制在特定环境中

日期限制

days 方法可用于将任务的执行限制在一周中的特定几天。例如,您可以安排命令在星期日和星期三每小时运行一次:

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

或者,您可以使用Illuminate\Console\Scheduling\Schedule 定义任务应该运行的日期时的类:

use Illuminate\Console\Scheduling\Schedule;

$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']);

Timezones

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

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

如果您重复为所有计划任务分配相同的时区,您可能希望定义一个scheduleTimezone 你的方法App\Console\Kernel 班级。此方法应返回应分配给所有计划任务的默认时区:

use DateTimeZone;

/**
 * Get the timezone that should be used by default for scheduled events.
 */
protected function scheduleTimezone(): DateTimeZone|string|null
{
    return 'America/Chicago';
}

Warning
请记住,某些时区使用夏令时。当发生夏令时更改时,您的计划任务可能会运行两次甚至根本不运行。出于这个原因,我们建议尽可能避免时区调度。

防止任务重叠

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

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

在这个例子中,emails:send 工匠命令 如果尚未运行,将每分钟运行一次。这withoutOverlapping 如果您的任务在执行时间上变化很大,这会阻止您准确预测给定任务将花费多长时间,则该方法特别有用。

如果需要,您可以指定在“无重叠”锁定到期之前必须经过多少分钟。默认情况下,锁将在 24 小时后过期:

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

在幕后,withoutOverlapping 方法利用您的应用程序的cache 获得锁。如有必要,您可以使用schedule:clear-cache 工匠命令。这通常只有在任务因意外的服务器问题而卡住时才有必要。

在一台服务器上运行任务

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

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

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

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

命名单个服务器作业

有时您可能需要安排同一个作业使用不同的参数进行调度,同时仍然指示 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();

后台任务

默认情况下,同时安排的多个任务将根据它们在您的文件中定义的顺序依次执行schedule 方法。如果您有长时间运行的任务,这可能会导致后续任务的启动时间比预期晚得多。如果您想在后台运行任务以便它们可以同时运行,您可以使用runInBackground 方法:

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

Warning
runInBackground 方法只能在通过commandexec 方法。

维护模式

您的应用程序的计划任务将不会在应用程序处于维护模式,因为我们不希望您的任务干扰您可能在服务器上执行的任何未完成的维护。然而,如果你想强制一个任务在维护模式下运行,你可以调用evenInMaintenanceMode 定义任务时的方法:

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

运行调度程序

现在我们已经了解了如何定义计划任务,让我们讨论如何在我们的服务器上实际运行它们。这schedule:run Artisan 命令将评估您所有的计划任务,并根据服务器的当前时间确定它们是否需要运行。

所以,当使用 Laravel 的调度器时,我们只需要在我们的服务器上添加一个 cron 配置条目来运行schedule:run 每分钟指挥一次。如果您不知道如何将 cron 条目添加到您的服务器,请考虑使用诸如Laravel 锻造 它可以为您管理 cron 条目:

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

在本地运行调度程序

通常,您不会将调度程序 cron 条目添加到本地开发机器。相反,您可以使用schedule:work 工匠命令。此命令将在前台运行并每分钟调用一次调度程序,直到您终止该命令:

php artisan schedule:work

任务输出

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

$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 方法是专有的commandexec 方法。

任务挂钩

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

$schedule->command('emails:send')
         ->daily()
         ->before(function () {
             // The task is about to execute...
         })
         ->after(function () {
             // The task has executed...
         });

onSuccessonFailure 方法允许您指定在计划任务成功或失败时要执行的代码。失败表示计划的 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...
         });

ping 网址

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

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

pingBeforeIfthenPingIf 仅当给定条件满足时,方法才可用于 ping 给定 URLtrue:

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

pingOnSuccesspingOnFailure 方法可用于仅在任务成功或失败时 ping 给定的 URL。失败表示计划的 Artisan 或系统命令以非零退出代码终止:

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

所有 ping 方法都需要 Guzzle HTTP 库。 Guzzle 通常默认安装在所有新的 Laravel 项目中,但是,如果它被意外删除,您可以使用 Composer 包管理器手动将 Guzzle 安装到您的项目中:

composer require guzzlehttp/guzzle

Events

如果需要,您可以收听events 由调度器调度。通常,事件侦听器映射将在您的应用程序的App\Providers\EventServiceProvider 班级:

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'Illuminate\Console\Events\ScheduledTaskStarting' => [
        'App\Listeners\LogScheduledTaskStarting',
    ],

    'Illuminate\Console\Events\ScheduledTaskFinished' => [
        'App\Listeners\LogScheduledTaskFinished',
    ],

    'Illuminate\Console\Events\ScheduledBackgroundTaskFinished' => [
        'App\Listeners\LogScheduledBackgroundTaskFinished',
    ],

    'Illuminate\Console\Events\ScheduledTaskSkipped' => [
        'App\Listeners\LogScheduledTaskSkipped',
    ],

    'Illuminate\Console\Events\ScheduledTaskFailed' => [
        'App\Listeners\LogScheduledTaskFailed',
    ],
];
豫ICP备18041297号-2