Планування Завдань
Вступ
У минулому ви могли написати запис конфігурації 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();
})
Якщо ви хочете переглянути огляд ваших запланованих завдань і час їх наступного запуску, ви можете скористатися командою Artisan schedule:list:
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 Closure команд
Якщо ви хочете запланувати 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();
Планування Shell команд
Метод exec може бути використаний для виконання команди в операційній системі:
use Illuminate\Support\Facades\Schedule;
Schedule::exec('node /home/forge/script.js')->daily();
Параметри Частоти Розкладу
Ми вже бачили кілька прикладів того, як можна налаштувати завдання для виконання через задані інтервали. Однак, існує багато інших частот розкладу завдань, які ви можете призначити завданню:
| Метод | Опис |
|---|---|
->cron('* * * * *'); | Виконувати завдання за власним розкладом cron. |
->everySecond(); | Виконувати завдання щосекунди. |
->everyTwoSeconds(); | Виконувати завдання кожні дві секунди. |
->everyFiveSeconds(); | Виконувати завдання кожні п’ять секунд. |
->everyTenSeconds(); | Виконувати завдання кожні десять секунд. |
->everyFifteenSeconds(); | Виконувати завдання кожні п’ятнадцять секунд. |
->everyTwentySeconds(); | Виконувати завдання кожні двадцять секунд. |
->everyThirtySeconds(); | Виконувати завдання кожні тридцять секунд. |
->everyMinute(); | Виконувати завдання щохвилини. |
->everyTwoMinutes(); | Виконувати завдання кожні дві хвилини. |
->everyThreeMinutes(); | Виконувати завдання кожні три хвилини. |
->everyFourMinutes(); | Виконувати завдання кожні чотири хвилини. |
->everyFiveMinutes(); | Виконувати завдання кожні п’ять хвилин. |
->everyTenMinutes(); | Виконувати завдання кожні десять хвилин. |
->everyFifteenMinutes(); | Виконувати завдання кожні п’ятнадцять хвилин. |
->everyThirtyMinutes(); | Виконувати завдання кожні тридцять хвилин. |
->hourly(); | Виконувати завдання щогодини. |
->hourlyAt(17); | Виконувати завдання щогодини на 17-й хвилині. |
->everyOddHour($minutes = 0); | Виконувати завдання щогодини з непарною годиною. |
->everyTwoHours($minutes = 0); | Виконувати завдання кожні дві години. |
->everyThreeHours($minutes = 0); | Виконувати завдання кожні три години. |
->everyFourHours($minutes = 0); | Виконувати завдання кожні чотири години. |
->everySixHours($minutes = 0); | Виконувати завдання кожні шість годин. |
->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'); | Виконувати завдання щороку 1 червня о 17:00. |
->timezone('America/New_York'); | Встановити часовий пояс для завдання. |
Ці методи можуть бути поєднані з додатковими обмеженнями для створення ще більш точно налаштованих розкладів, які виконуються лише в певні дні тижня. Наприклад, ви можете запланувати команду на щотижневе виконання в понеділок:
use Illuminate\Support\Facades\Schedule; // Виконувати раз на тиждень у понеділок о 13:00... Schedule::call(function () { // ... })->weekly()->mondays()->at('13:00'); // Виконувати щогодини з 8:00 до 17:00 у робочі дні... 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.
Обмеження середовища
Метод environments може бути використаний для виконання завдань лише в заданих середовищах (як визначено змінною середовища APP_ENV environment variable):
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' => '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();
Ви можете використовувати метод 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();
Фонові завдання
За замовчуванням, кілька завдань, запланованих на той самий час, виконуватимуться послідовно відповідно до порядку, в якому вони визначені у вашому методі schedule. Якщо у вас є завдання, що виконуються довго, це може призвести до того, що наступні завдання почнуть виконуватися набагато пізніше, ніж очікувалося. Якщо ви хочете виконувати завдання у фоновому режимі, щоб вони могли виконуватися одночасно, ви можете використовувати метод runInBackground:
use Illuminate\Support\Facades\Schedule;
Schedule::command('analytics:report')
->daily()
->runInBackground();
Метод 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');
});
Запуск Планувальника
Тепер, коли ми дізналися, як визначати заплановані завдання, давайте обговоримо, як їх фактично запускати на нашому сервері. Команда Artisan schedule:run оцінить всі ваші заплановані завдання і визначить, чи потрібно їх виконувати, виходячи з поточного часу сервера.
Отже, при використанні планувальника 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 планувальника на вашу локальну машину розробки. Натомість ви можете використовувати команду Artisan schedule:work. Ця команда буде виконуватись у передньому плані та викликати планувальник кожну хвилину, доки ви не завершите команду. Коли визначені завдання з інтервалом менше хвилини, планувальник продовжуватиме працювати протягом кожної хвилини для обробки цих завдань:
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('example@example.com');
Якщо ви хочете надіслати електронний лист з результатом лише у випадку, якщо запланована команда Artisan або системна команда завершується з ненульовим кодом виходу, використовуйте метод emailOutputOnFailure:
Schedule::command('report:generate')
->daily()
->emailOutputOnFailure('example@example.com');
Методи emailOutputTo, emailOutputOnFailure, sendOutputTo та appendOutputTo є ексклюзивними для методів 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);
Методи pingOnSuccess та pingOnFailure можуть бути використані для пінгу заданого URL лише у випадку успіху або невдачі завдання. Невдача вказує на те, що запланована команда Artisan або системна команда завершилася з ненульовим кодом виходу:
Schedule::command('emails:send')
->daily()
->pingOnSuccess($successUrl)
->pingOnFailure($failureUrl);
Методи pingBeforeIf, thenPingIf, pingOnSuccessIf та pingOnFailureIf можуть бути використані для пінгу заданого URL лише якщо задана умова є true:
Schedule::command('emails:send')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);
Schedule::command('emails:send')
->daily()
->pingOnSuccessIf($condition, $successUrl)
->pingOnFailureIf($condition, $failureUrl);
Події
Laravel виконує різноманітні події під час процесу планування. Ви можете визначити слухачів для будь-якої з наступних подій:
| Назва події |
|---|
Illuminate\Console\Events\ScheduledTaskStarting |
Illuminate\Console\Events\ScheduledTaskFinished |
Illuminate\Console\Events\ScheduledBackgroundTaskFinished |
Illuminate\Console\Events\ScheduledTaskSkipped |
Illuminate\Console\Events\ScheduledTaskFailed |
