Планирование задач
Введение
В прошлом вы, возможно, написали запись конфигурации cron для каждой задачи, которую вам нужно было запланировать на своем сервере. Однако это может быстро стать проблемой, потому что ваше расписание задач больше не находится в системе управления версиями, и вы должны подключиться к серверу по SSH, чтобы просмотреть существующие записи cron или добавить дополнительные записи.
Планировщик команд Laravel предлагает новый подход к управлению запланированными задачами на вашем сервере. Планировщик позволяет вам свободно и выразительно определять расписание команд в самом приложении Laravel. При использовании планировщика на вашем сервере требуется только одна запись cron. Расписание вашей задачи определяется в методе schedule
файла app/Console/Kernel.php
. Чтобы помочь вам начать работу, в методе определен простой пример.
Определение расписаний
Вы можете определить все свои запланированные задачи в методе 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. * * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */ protected function schedule(Schedule $schedule) { $schedule->call(function () { DB::table('recent_users')->delete(); })->daily(); }}
В дополнение к планированию с использованием замыканий вы также можете планировать вызываемые объекты. Вызываемые объекты — это простые классы PHP, которые содержат метод __invoke
:
$schedule->call(new DeleteRecentUsers)->daily();
Если вы хотите просмотреть обзор ваших запланированных задач и время их следующего запуска, вы можете использовать Artisan-команду schedule:list
:
php artisan schedule:list
Artisan-команды планирования
Помимо планирования закрытия, вы также можете запланировать 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();
Планирование команд оболочки
Метод exec
может использоваться для выдачи команды операционной системе:
$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 |
->twiceMonthly(1, 16, '13:00'); |
Выполнять задание ежемесячно 1 и 16 числа в 13:00 |
->lastDayOfMonth('15:00'); |
Выполнять задание в последний день месяца в 15:00 |
->quarterly(); |
Выполнять задачу в первый день каждого квартала в 00:00 |
->yearly(); |
Запускать задачу в первый день каждого года в 00:00 |
->yearlyOn(6, 1, '17:00'); |
Выполнять задание каждый год 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');
Список дополнительных ограничений расписания можно найти ниже:
Метод | Описание |
---|---|
->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']);
Часовые пояса
Используя метод timezone
, вы можете указать, что время запланированной задачи должно интерпретироваться в пределах заданного часового пояса:
$schedule->command('report:generate') ->timezone('America/New_York') ->at('2:00')
Если вы постоянно назначаете один и тот же часовой пояс всем своим запланированным задачам, вы можете определить метод scheduleTimezone
в своем классе App\Console\Kernel
. Этот метод должен возвращать часовой пояс по умолчанию, который должен быть назначен всем запланированным задачам:
/** * Get the timezone that should be used by default for scheduled events. * * @return \DateTimeZone|string|null */protected function scheduleTimezone(){ return 'America/Chicago';}
{note} Помните, что в некоторых часовых поясах используется летнее время. Когда происходит переход на летнее время, запланированная задача может запускаться дважды или даже не запускаться вообще. По этой причине мы рекомендуем по возможности избегать планирования по часовым поясам.
Предотвращение дублирования задач
По умолчанию запланированные задачи будут выполняться, даже если предыдущий экземпляр задачи все еще выполняется. Чтобы предотвратить это, вы можете использовать метод withoutOverlapping
:
$schedule->command('emails:send')->withoutOverlapping();
В этом примере emails:send
команда Artisan будет запускаться каждую минуту, если она еще не запущена. Метод withoutOverlapping
особенно полезен, если у вас есть задачи, время выполнения которых резко различается, что не позволяет точно предсказать, сколько времени займет выполнение данной задачи.
При необходимости вы можете указать, сколько минут должно пройти до истечения срока действия блокировки «без перекрытия». По умолчанию срок действия блокировки истекает через 24 часа:
$schedule->command('emails:send')->withoutOverlapping(10);
Запуск задач на одном сервере
{note} Чтобы использовать эту функцию, ваше приложение должно использовать драйвер кеша
database
,memcached
,dynamodb
илиredis
в качестве драйвера кеша вашего приложения по умолчанию. Кроме того, все серверы должны обмениваться данными с одним и тем же сервером центрального кэша.
Если планировщик вашего приложения работает на нескольких серверах, вы можете ограничить выполнение запланированного задания только на одном сервере. Например, предположим, что у вас есть запланированная задача, которая создает новый отчет каждую пятницу вечером. Если планировщик задач запущен на трех рабочих серверах, запланированная задача будет выполняться на всех трех серверах и формировать отчет три раза. Нехорошо!
Чтобы указать, что задача должна выполняться только на одном сервере, используйте метод onOneServer
при определении запланированной задачи. Первый сервер, получивший задание, обеспечит атомарную блокировку задания, чтобы другие серверы не могли выполнять одно и то же задание одновременно:
$schedule->command('report:generate') ->fridays() ->at('17:00') ->onOneServer();
Фоновые задачи
По умолчанию несколько задач, запланированных одновременно, будут выполняться последовательно в соответствии с порядком, в котором они определены в вашем методе schedule
. Если у вас есть длительные задачи, это может привести к тому, что последующие задачи будут запускаться намного позже, чем предполагалось. Если вы хотите запускать задачи в фоновом режиме, чтобы все они могли выполняться одновременно, вы можете использовать метод runInBackground
:
$schedule->command('analytics:report') ->daily() ->runInBackground();
{note} Метод
runInBackground
можно использовать только при планировании задач с помощью методовcommand
иexec
.
Режим обслуживания
Запланированные задачи вашего приложения не будут выполняться, когда приложение находится в режиме обслуживания, поскольку мы не хотим, чтобы ваши задачи мешали любому незавершенному обслуживанию, которое вы можете выполнять. выполнение на вашем сервере. Однако, если вы хотите принудительно запустить задачу даже в режиме обслуживания, вы можете вызвать метод evenInMaintenanceMode
при определении задачи:
$schedule->command('emails:send')->evenInMaintenanceMode();
Запуск планировщика
Теперь, когда мы узнали, как определять запланированные задачи, давайте обсудим, как на самом деле запускать их на нашем сервере. Команда Artisan schedule:run
оценит все ваши запланированные задачи и определит, нужно ли их запускать, исходя из текущего времени сервера.
Итак, при использовании планировщика Laravel нам нужно всего лишь добавить одну запись конфигурации cron на наш сервер, которая запускает команду schedule:run
каждую минуту. Если вы не знаете, как добавить записи cron на свой сервер, рассмотрите возможность использования службы, такой как Laravel Forge, которая может управлять записями cron для вас:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
Локальный запуск планировщика
Как правило, вы не добавляете запись cron планировщика на свой локальный компьютер разработки. Вместо этого вы можете использовать Artisan-команду schedule:work
. Эта команда будет выполняться на переднем плане и вызывать планировщик каждую минуту, пока вы не завершите команду:
php artisan schedule:work
Вывод задачи
Планировщик Laravel предоставляет несколько удобных методов для работы с выводом, созданным запланированными задачами. Во-первых, используя метод sendOutputTo
, вы можете отправить вывод в файл для последующей проверки:
$schedule->command('emails:send') ->daily() ->sendOutputTo($filePath);
Если вы хотите добавить вывод в данный файл, вы можете использовать метод appendOutputTo
:
$schedule->command('emails:send') ->daily() ->appendOutputTo($filePath);
Используя метод emailOutputTo
, вы можете отправить вывод на адрес электронной почты по вашему выбору. Перед отправкой результатов задачи по электронной почте вы должны настроить почтовые службы:
$schedule->command('report:generate') ->daily() ->sendOutputTo($filePath) ->emailOutputTo('taylor@example.com');
Если вы хотите отправить вывод по электронной почте только в том случае, если запланированная Artisan или системная команда завершается с ненулевым кодом выхода, используйте метод emailOutputOnFailure
:
$schedule->command('report:generate') ->daily() ->emailOutputOnFailure('taylor@example.com');
{note} Методы
emailOutputTo
,emailOutputOnFailure
,sendOutputTo
иappendOutputTo
являются эксклюзивными для методовcommand
иexec
.
Хуки для задач
Используя методы before
и after
, вы можете указать код, который будет выполняться до и после выполнения запланированной задачи:
$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... });
Проверка URL-адресов
Используя методы pingBefore
и thenPing
, планировщик может автоматически пинговать заданный URL-адрес до или после выполнения задачи. Этот метод полезен для уведомления внешней службы, такой как Envoyer, о том, что ваша запланированная задача начинает или завершила выполнение:
$schedule->command('emails:send') ->daily() ->pingBefore($url) ->thenPing($url);
Методы pingBeforeIf
и thenPingIf
могут использоваться для проверки связи с заданным URL-адресом только в том случае, если заданное условие true
:
$schedule->command('emails:send') ->daily() ->pingBeforeIf($condition, $url) ->thenPingIf($condition, $url);
Методы pingOnSuccess
и pingOnFailure
могут использоваться для проверки связи с заданным URL-адресом только в случае успешного или неудачного выполнения задачи. Ошибка указывает на то, что запланированная Artisan или системная команда завершилась с ненулевым кодом выхода:
$schedule->command('emails:send') ->daily() ->pingOnSuccess($successUrl) ->pingOnFailure($failureUrl);
Для всех методов ping требуется HTTP-библиотека Guzzle. Guzzle обычно устанавливается во всех новых проектах Laravel по умолчанию, но вы можете вручную установить Guzzle в свой проект с помощью диспетчера пакетов Composer, если он был случайно удален:
composer require guzzlehttp/guzzle
События
При необходимости вы можете прослушивать события, отправленные планировщиком. Как правило, сопоставления прослушивателей событий будут определены в классе вашего приложения 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', ],];