Планирование задач
- Введение
- Определение расписаний
- Планирование команд Artisan
- Планирование отправки заданий в очереди
- Планирование команд операционной системы
- Параметры периодичности расписания
- Часовые пояса
- Предотвращение дублирования задач
- Выполнение задач на одном сервере
- Фоновые задачи
- Режим технического обслуживания
- Запуск планировщика
- Задания с интервалом менее минуты
- Локальный запуск планировщика
- Результат выполнения задачи
- Хуки выполнения задачи
- События
Введение
В прошлом вы могли создавать запись конфигурации 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();
В дополнение к планированию с использованием замыканий вы также можете использовать вызываемые объекты. Вызываемые объекты – это простые классы PHP, содержащие метод __invoke
:
Schedule::call(new DeleteRecentUsers)->daily();
Если вы предпочитаете зарезервировать файл routes/console.php
только для определений команд, вы можете использовать метод withSchedule
в файле bootstrap/app.php
вашего приложения для определения запланированных задач. Этот метод принимает замыкание, которое получает экземпляр планировщика:
use Illuminate\Console\Scheduling\Schedule;
->withSchedule(function (Schedule $schedule) {
$schedule->call(new DeleteRecentUsers)->daily();
})
Если вы хотите просмотреть список ваших запланированных задач и их последующего запуска, то вы можете использовать команду schedule:list
Artisan:
php artisan schedule:list
Планирование команд Artisan
В дополнение к планированию с использованием замыканий вы также можете использовать команды 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, определенную замыканием, вы можете связать методы, связанные с планированием, после определения команды:
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;
// Отправляем задание в очередь «heartbeats» соединения «sqs» ...
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
Планирование команд операционной системы
Метод exec
используется для передачи команды операционной системе:
use Illuminate\Support\Facades\Schedule;
Schedule::exec('node /home/forge/script.js')->daily();
Параметры периодичности расписания
Мы уже видели несколько примеров того, как можно настроить задачу на выполнение через определенные промежутки времени. Однако существует гораздо больше параметров планирования, которые можно назначить задаче:
Метод | Описание |
---|---|
->cron('* * * * *'); |
Запустить задачу по расписанию с параметрами cron |
->everySecond(); |
Запускать задачу ежесекундно |
->everyTwoSeconds(); |
- каждые 2 секунды |
->everyFiveSeconds(); |
- каждые 5 секунд |
->everyTenSeconds(); |
- каждые 10 секунд |
->everyFifteenSeconds(); |
- каждые 15 секунд |
->everyTwentySeconds(); |
- каждые 20 секунд |
->everyThirtySeconds(); |
- каждые 30 секунд |
->everyMinute(); |
Запускать задачу ежеминутно |
->everyTwoMinutes(); |
– каждые 2 минуты |
->everyThreeMinutes(); |
– каждые 3 минуты |
->everyFourMinutes(); |
– каждые 4 минуты |
->everyFiveMinutes(); |
– каждые 5 минут |
->everyTenMinutes(); |
– каждые 10 минут |
->everyFifteenMinutes(); |
– каждые 15 минут |
->everyThirtyMinutes(); |
– каждые 30 минут |
->hourly(); |
– каждый час |
->hourlyAt(17); |
– в 17 минут каждого часа |
->everyOddHour($minutes = 0); |
- каждый нечетный час |
->everyTwoHours($minutes = 0); |
- каждые 2 часа |
->everyThreeHours($minutes = 0); |
- каждые 3 часа |
->everyFourHours($minutes = 0); |
- каждые 4 часа |
->everySixHours($minutes = 0); |
- каждые 6 часов |
->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'); |
– ежегодно в июне первого числа в 17:00 |
->timezone('America/New_York'); |
Установить часовой пояс для задачи |
Эти методы можно комбинировать с дополнительными ограничениями для создания еще более точных расписаний, которые выполняются только в определенные дни недели. Например, вы можете запланировать выполнение команды еженедельно в понедельник:
use Illuminate\Support\Facades\Schedule;
// Запускаем раз в неделю в понедельник в 13:00 ...
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($startTime, $endTime); |
– временными интервалами начала и окончания |
->unlessBetween($startTime, $endTime); |
– через исключение временных интервалов начала и окончания |
->when(Closure); |
– на основе истинности результата выполненного замыкания |
->environments($env); |
– окружением выполнения |
Дневные ограничения
Метод 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
.
Ограничения окружения выполнения
Метод environment
может использоваться для выполнения задач только в указанных окружениях, согласно определению переменной 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')
Если вы постоянно назначаете один и тот же часовой пояс для всех запланированных задач, то вы можете указать, какой часовой пояс должен быть назначен всем расписаниям, определив параметр schedule_timezone
в файле конфигурации app
вашего приложения:
'timezone' => env('APP_TIMEZONE', 'UTC'),
'schedule_timezone' => 'America/Chicago',
Помните, что в некоторых часовых поясах используется летнее время. Когда происходит переход на летнее время, ваша запланированная задача может запускаться дважды или даже не запускаться вообще. По этой причине мы рекомендуем по возможности избегать указаний часовых поясов при планировании.
Предотвращение дублирования задач
По умолчанию запланированные задачи будут выполняться, даже если предыдущий экземпляр задачи все еще выполняется. Чтобы предотвратить это, вы можете использовать метод withoutOverlapping
:
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->withoutOverlapping();
В этом примере команда emails:send
Artisan будет запускаться каждую минуту при условии, что она еще не запущена. Метод withoutOverlapping
особенно полезен, если у вас есть задачи, которые разнятся по времени выполнения, что не позволяет вам точно предсказать, сколько времени займет текущая задача.
При необходимости вы можете указать, сколько минут должно пройти до окончания блокировки «перекрывающихся» задач. По умолчанию срок блокировки истекает через 24 часа:
Schedule::command('emails:send')->withoutOverlapping(10);
Внутри метод withoutOverlapping
использует кэш вашего приложения для получения блокировок. При необходимости вы можете очистить эти блокировки, используя команду Artisan schedule:clear-cache
. Обычно это необходимо только в случае, если задача застревает из-за непредвиденной проблемы с сервером.
Выполнение задач на одном сервере
Чтобы использовать этот функционал, ваше приложение должно использовать по умолчанию один из следующих драйверов кеша:
database
,memcached
,dynamodb
, илиredis
. Кроме того, все серверы должны взаимодействовать с одним и тем же центральным сервером кеширования.
Если планировщик вашего приложения работает на нескольких серверах, то вы можете ограничить выполнение запланированного задания только на одном сервере. Например, предположим, что у вас есть запланированная задача, по которой каждую пятницу вечером создается новый отчет. Если планировщик задач работает на трех рабочих серверах, запланированная задача будет запущена на всех трех серверах и трижды сгенерирует отчет. Не очень хорошо!
Чтобы указать, что задача должна выполняться только на одном сервере, используйте метод onOneServer
при определении запланированной задачи. Первый сервер, который получит задачу, обеспечит атомарную блокировку задания, чтобы другие серверы не могли одновременно выполнять ту же задачу:
use Illuminate\Support\Facades\Schedule;
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
:
use Illuminate\Support\Facades\Schedule;
Schedule::command('analytics:report')
->daily()
->runInBackground();
Метод
runInBackground
может использоваться только при планировании задач с помощью методовcommand
иexec
.
Режим технического обслуживания
Запланированные задачи вашего приложения не будут выполняться, когда приложение находится в режиме обслуживания, поскольку мы не хотим, чтобы ваши задачи мешали любому незавершенному процессу обслуживания, выполняющемуся на вашем сервере. Однако, если вы хотите принудительно запустить задачу даже в режиме обслуживания, то используйте метод evenInMaintenanceMode
при определении задачи:
Schedule::command('emails:send')->evenInMaintenanceMode();
Запуск планировщика
Теперь, когда мы узнали, как определять планирование задачи, давайте обсудим, как же запускать их на нашем сервере. Команда schedule:run
Artisan проанализирует все ваши запланированные задачи и определит, нужно ли их запускать, исходя из текущего времени сервера.
Итак, при использовании планировщика Laravel нам нужно добавить только одну конфигурационную запись cron на наш сервер, которая запускает команду schedule:run
каждую минуту. Если вы не знаете, как добавить записи cron на свой сервер, то рассмотрите возможность использования такой службы, как Laravel Forge, которая может управлять записями cron за вас:
* * * * * 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:work
Результат выполнения задачи
Планировщик Laravel предлагает несколько удобных методов для работы с выводом результатов, созданных запланированными задачами. Во-первых, используя метод 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');
Методы
emailOutputTo
,emailOutputOnFailure
,sendOutputTo
, andappendOutputTo
могут использоваться только при планировании задач с помощью методовcommand
иexec
.
Хуки выполнения задачи
Используя методы before
и after
, вы можете указать замыкания, которые будут выполняться до и после выполнения запланированной задачи:
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->before(function () {
// Задача готова к выполнению ...
})
->after(function () {
// Задача выполнена ...
});
Методы onSuccess
и onFailure
позволяют указать замыкания, которые будут выполняться в случае успешного или неудачного выполнения запланированной задачи. Ошибка означает, что запланированная (Artisan или системная) команда завершилась ненулевым кодом возврата:
Schedule::command('emails:send')
->daily()
->onSuccess(function () {
// Задача успешно выполнена ...
})
->onFailure(function () {
// Не удалось выполнить задачу ...
});
Если из вашей команды доступен вывод результата, то вы можете получить к нему доступ в ваших хуках after
, onSuccess
или onFailure
, указав тип экземпляра Illuminate\Support\Stringable
в качестве аргумента $output
замыкания при определении вашего хука:
use Illuminate\Support\Stringable;
Schedule::command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// Задача успешно выполнена ...
})
->onFailure(function (Stringable $output) {
// Не удалось выполнить задачу ...
});
Пингование URL-адресов
Используя методы pingBefore
и thenPing
, планировщик может автоматически пинговать по-указанному URL до или после выполнения задачи. Этот метод полезен для уведомления внешней службы, такой как Envoyer, о том, что ваша запланированная задача запущена или завершена:
Schedule::command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);
Методы pingBeforeIf
и thenPingIf
могут использоваться для пингования по указанному URL, только если переданное условие $condition
истинно:
Schedule::command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
Методы pingOnSuccess
и pingOnFailure
могут использоваться для пингования по-указанному URL только в случае успешного или неудачного выполнения задачи. Ошибка означает, что запланированная (Artisan или системная) команда завершилась ненулевым кодом возврата:
Schedule::command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);
События
Laravel отправляет различные события в процессе планирования. Вы можете определить прослушиватели для любого из следующих событий:
Наименование события |
---|
Illuminate\Console\Events\ScheduledTaskStarting |
Illuminate\Console\Events\ScheduledTaskFinished |
Illuminate\Console\Events\ScheduledBackgroundTaskFinished |
Illuminate\Console\Events\ScheduledTaskSkipped |
Illuminate\Console\Events\ScheduledTaskFailed |