- Laravel пропонує єдиний API черг з кількома драйверами (база даних, Redis, SQS…) та розділенням між з'єднаннями та логічними чергами.
- Завдання дозволяють переносити важкі завдання у фоновий режим, з підтримкою серіалізації моделей, проміжного програмного забезпечення, одноразових завдань, шифрування, пакетів та рядків.
- Керування працівниками здійснюється за допомогою queue:work, Supervisor та таких команд, як queue:pause/queue:continue, що контролюють повторні спроби, тайм-аути та пріоритети.
- Існує повноцінна екосистема для збоїв, моніторингу та тестування (failed_jobs, queue:retry, queue:monitor, Queue::fake), що робить черги надійними у продакшені.
Робота з чергами в Laravel майже обов'язкова в будь-якому більш-менш серйозному проекті: надсилання електронних листів, обробка CSV-файлів, створення PDF-файлів, зміна розміру зображень, виклик зовнішніх API… Якщо все це робиться в основному HTTP-запиті, веб-сайт стає повільним, з'являється 500 помилок, і користувач зрештою відчайдушно дивиться на порожній екран.
Гарна новина полягає в тому, що Laravel пропонує потужну та гнучку інфраструктуру черг , яка дозволяє делегувати важку роботу фоновим процесам, керувати ними за пріоритетом, групувати їх у пакети, контролювати їх, повторювати спроби у разі збою та навіть тимчасово призупиняти виконавці для обслуговування. Давайте уважно розглянемо (і нічого не пропустимо), як отримати максимальну віддачу від нього, зокрема як призупиняти, відновлювати та контролювати його поведінку у продакшені.
Основні поняття: з'єднання, черги та драйвери
У Laravel, перш ніж щось призупиняти, потрібно зрозуміти різницю між з'єднання черг та самі черги, в config/queue.php у вас є масив connections де ви визначаєте кожен бекенд: база даних, redis, beanstalkd, sqs, синхронізація, null, резервне копіювання, rabbitmq (якщо ви його додасте) тощо.
Кожне з'єднання може мати кілька логічні черги (такі імена, як default, emails, high, low, importsАтрибут queue Кожне з'єднання вказує, яка черга за замовчуванням буде розміщена в ній, якщо не вказано інше. onQueue()Таким чином ви можете робити такі речі, як:
- Швидка черга для електронних листів або коротких транзакцій.
- Ще один низький пріоритет для тривалих завдань, таких як обробка відео або великих звітів.
- Черги, сегментовані за типом завдання:
imports,notificaciones,reportes, І т.д.
Коли ви запускаєте працівника за допомогою php artisan queue:workЗа замовчуванням він прослуховує чергу, вказану як за замовчуванням на вибраному з’єднанні. Якщо ви хочете встановити пріоритети, ви можете передати кілька черг, розділених комами:
php artisan queue:work redis --queue=high,default,low
Таким чином, воркер спочатку обробляє high , потім default і лише потім low , що є критично важливим, коли система завантажена, і ви не хочете, щоб 30-хвилинний звіт блокував електронні листи з високим пріоритетом.
Драйвери черг та передумови
Laravel пропонує уніфікований API для кількох драйверів черг , але кожен має свої специфічні функції та залежності. Вибір правильного драйвера є критично важливим, перш ніж обговорювати призупинення воркерів.
База даних драйверів
Водій база даних Він зберігає завдання в таблиці SQL (зазвичай jobsЙого легко налаштувати, і він ідеально підходить для невеликих проектів або середовищ розробки, де ви не хочете возитися з Redis або SQS.
Основні кроки:
- Згенеруйте міграцію:
php artisan queue:table(або в новіших версіях, він постачається якcreate_jobs_tableза замовчуванням). - Виконати міграції:
php artisan migrate. - Налаштувати QUEUE_CONNECTION=база даних в
.envі підтвердити вconfig/queue.phpщо'default' => env('QUEUE_CONNECTION','database').
Це зручно та достатньо надійно, але з великою кількістю черг та трафіку може стати вузьким місцем , оскільки все проходить через одну й ту саму реляційну базу даних.
Драйвер Redis
Драйвер Redis — це стандартна робоча конячка у продакшені. Він швидкий, підтримує розширені функції (блокування, обмеження швидкості, атомарні блокування, Horizon тощо) та дуже добре масштабується.
Важливі моменти:
- Вам потрібно налаштувати підключення Redis у
config/database.php. - Варіанти
serializerycompressionз Redis Вони не сумісні з водієм черги. - Якщо ви використовуєте Кластер РедісНазви черг повинні мати хештег, наприклад
{default}щоб усі ключі в цій черзі потрапляли в один і той самий слот. - Вибір
block_forЦе дозволяє працівнику блокуватися, очікуючи на завдання протягом кількох секунд, замість безперервного опитування: економить процесор та зменшує кількість запитів до сервера Redis.
Наприклад:
'redis' => ,
Якщо ви покладете блок_для = 0Працівник залишається заблокованим на невизначений термін, доки не стане доступним завдання, про що слід пам’ятати, якщо ви хочете, щоб він швидко реагував на такі сигнали, як SIGTERM або команди паузи.
Інші драйвери: SQS, Beanstalkd, null, перемикання на інший ресурс…
Окрім баз даних та Redis, Laravel підтримує інші системи :
- Amazon SQSКеровані черги AWS з підтримкою FIFOГрупи повідомлень, дедуплікація та розширені налаштування видимості. Потрібен SDK.
aws/aws-sdk-php. - Бобове стеблоДуже швидко та легко, використовуючи пакет
pda/pheanstalk. - синхронізувати: виконує завдання inmediately в тому ж процесі. Корисно в розробці або тестуванні, але у виробництві це практично те саме, що взагалі не використовувати черги.
- нулю: Відхиляє завдання одразу після їх «відправлення». Використовується для повного вимкнення системи черг.
- відмовуЦе дозволяє налаштувати кілька підключень, і якщо основне з них не працює, Laravel намагається перенести завдання на наступне у списку. Ідеально підходить для середовищ, де висока доступність є критичним.
На рівні призупинення та відновлення воркерів логіка практично однакова незалежно від драйвера, оскільки вона керується командами Artisan та класом Worker , а не з бекенду черги.
Створення завдань та серіалізація моделі
Un робота У Laravel це просто клас, який інкапсулює завдання, що виконується в черзі. Зазвичай він генерується за допомогою Artisan та зберігається в app/Jobs.
Щоб створити один:
php artisan make:job ProcessPodcast
Згенерований клас знаряддя праці ShouldQueue і зазвичай використовує риси Dispatchable, InteractsWithQueue, Queueable y SerializesModelsСерце в методі handle()що й робить працівник, коли отримує завдання.
Особливо потужною функцією є автоматична серіалізація моделей Eloquent . Якщо ви приймаєте модель у конструкторі Job, Laravel не додає весь об'єкт до черги, а лише його ідентифікатор. Коли завдання обробляється, воно реконструює модель з бази даних із завантаженими зв'язками.
Типовий приклад (у псевдокоді, щоб уникнути дослівного повторення):
- Будівельник отримує Подкаст $podcast.
- Риса
SerializesModelsВін відповідає за збереження ідентифікатора. - En
handle(AudioProcessor $processor)Ви виконуєте важку роботу (наприклад, обробку аудіо).
Якщо ви не хочете, щоб зв'язки в моделі були серіалізовані (щоб корисне навантаження не було величезним), ви можете використовувати $model->withoutRelations() або в PHP 8 атрибут # на нерухомості, що рекламується забудовником.
Впровадження залежностей у метод дескриптора
Ще одна перевага полягає в тому, що ви можете вставляти сервіси безпосередньо в метод handle (ніби це контролер), оскільки Laravel також використовує контейнер сервісів у Jobs. Наприклад, ви можете запросити AudioProcessorHTTP-клієнти, репозиторії тощо, а фреймворк піклується про їх вирішення.
Якщо вам потрібен надточний контроль над тим, що називається handleви можете використовувати Container::bindMethod() у постачальника послуг та самостійно визначте логіку впровадження, але в більшості випадків немає потреби так сильно ускладнювати речі.
Проміжне програмне забезпечення для завдань
Так само, як маршрути мають проміжне програмне забезпечення, завдання також можуть мати специфічне проміжне програмне забезпечення які виконуються до та після завдання. Це значно зменшує кількість повторюваного коду в методах handle() і це особливо корисно для:
- Обмеження швидкості з Redis.
- уникнути перекриття творів, що стосуються одного й того ж ресурсу.
- Звичайні винятки для нестабільних API.
- Пропуск завдань за певних умов (
Skipпроміжне програмне забезпечення).
Проміжне програмне забезпечення зазвичай розміщується в app/Jobs/Middleware і повертаються з методу middleware() З книги Йова. Концептуальний приклад:
public function middleware(): array { return ;
Спеціальні типи завдань: унікальні, зашифровані та заблоковані
Laravel дозволяє позначати певні завдання як унікальний щоб гарантувати, що жодні два однакові екземпляри ніколи не будуть поставлені в чергу одночасно. Це досягається шляхом реалізації інтерфейсу ShouldBeUnique o ShouldBeUniqueUntilProcessingта підтримує визначення ключа uniqueId() і час uniqueFor.
Це ідеально підходить для таких речей, як:
- Перерахувати пошуковий індекс продукту: лише один раз на кожен продукт поки не закінчиться.
- Синхронізація із зовнішніми сервісами, які не дозволяють одночасні запити до одного й того ж ресурсу.
Нижче, Laravel використовує a атомний замок кешу (Redis, memcached, база даних тощо). Ви також можете вибрати драйвер кешу для цього блокування за допомогою uniqueVia().
Крім того, є інтерфейс ShouldBeEncrypted, за допомогою якого ви вказуєте, що Завдання виконано повністю Введіть свій код, перш ніж входити в чергуДуже корисно, коли передаєте конфіденційні дані, і ви не хочете, щоб вони передавалися у вигляді звичайного тексту через Redis, SQS або базу даних.
Відправлення, затримка та виконання завдань у різних режимах
Після того, як ваше завдання визначено, ви запускаєте його за допомогою Job::dispatch() від контролерів, слухачів подій, команд Artisan або навіть замикань. Параметри, які ви передаєте до dispatch Вони йдуть безпосередньо до будівельника Йова.
Цікаві варіації:
dispatchIf()ydispatchUnless()кондиціонувати відправлення в один рядок.delay()щоб завдання не могло бути оброблене до певної дати/часу.dispatchAfterResponse()oafterResponse()endispatch()Замикання, які відкладають виконання до відправлення HTTP-відповіді, але в межах того ж PHP-процесу.dispatchSync()запускати завдання синхронно (без проходження через чергу), але повторно використовуючи той самий клас.- Спеціальні з'єднання, такі як
deferredobackgroundвиконати після відповіді, в тому ж процесі або в новому PHP-процесі.
Нарешті, ви можете вказати з'єднання та чергу за допомогою ланцюгових методів. onConnection() y onQueue(), або під час відправлення, або в конструкторі завдань.
Точний контроль: повторні спроби, відстрочка, тайм-аути та обробка помилок
Важлива частина роботи з чергами в Laravel полягає у визначенні кількості повторних спроб виконання завдання , часу очікування між спробами та часу, коли вважати його невдалим.
Доступні варіанти:
--triesenqueue:workвстановити максимальну кількість спроб для всіх завдань цього працівника (якщо завдання не визначає свою власну кількість)$triesotries()).$triesабо методtries()у класі Job для керування кожним завданням окремо.retryUntil()сказати: «пробуй ще раз скільки завгодно, до цього часу, а після цього — провал». Якщо це збігається зtries, командаretryUntil.$maxExceptionsвідрізати, коли проблема пов'язана з повторюваними необробленими винятками.$timeouto--timeoutщоб вказати, скільки секунд може виконуватися завдання, перш ніж виконавець завершить дочірній процес.$failOnTimeoutпозначити завдання як невдале у разі закінчення часу очікування.$backoffметодbackoff()щоб визначити, скільки часу слід чекати між повторними спробами, навіть з експоненціальними значеннями.
Якщо завдання викине неперехоплений виняток, Laravel... випускає назад у хвіст доки не буде вичерпано налаштовані спроби. Ви можете керувати цим вручну за допомогою release($segundos) o fail($exception) коли ви хочете навмисно позначити завдання як невиконане.
Невдалі завдання, таблиця failed_jobs та скорочення
Коли Йов вичерпує свої спроби, його починають вважати невдала робота і Laravel вставляє його в таблицю failed_jobs (або в DynamoDB, якщо ви налаштуєте його таким чином). У цій таблиці зберігаються UUID, з’єднання, черга, корисне навантаження завдання та виняток, який був викликаний.
Корисні команди:
php artisan queue:failedперерахувати ті, що зазнали невдачі.php artisan queue:retry <id|all>спробувати їх ще раз.php artisan queue:forget <id>щоб видалити один.php artisan queue:flushвидалити все (з опцією для годин у новіших версіях).php artisan queue:prune-failed --hours=48автоматично видаляти старі записи.
У Jobs ви можете визначити метод failed(Throwable $exception) Для додаткового очищення: сповіщення користувача, скасування часткових дій, запис спеціалізованих журналів тощо. Зверніть увагу, що для виклику створюється новий екземпляр завдання. failed()Тому не покладайтеся на зміни властивостей, внесені в handle().
Пакетування завдань: пакети завдань та їх об'єднання за допомогою рядків
Коли вам потрібно запустити сотні або тисячі пов'язаних завдань (наприклад, імпорт гігантського CSV фрагментами, обробка файлів пакетами, очищення даних тощо), доцільно використовувати пакетну функціональність Laravel .
Основний потік:
- створити таблицю
job_batchesзphp artisan queue:batches-table && php artisan migrate. - Позначте завдання, які будуть частиною пакетів, за допомогою ознаки
Batchable. - Використовуйте
Bus::batch()надсилати набір завдань із зворотними викликамиthen,catchyfinally. - Отримайте доступ до пакету з кожного завдання за допомогою
$this->batch()і перевірте, чи його скасовано. - Можливість додавати більше завдань до поточної партії за допомогою
$this->batch()->add().
Пакети чудово інтегруються з ланцюгами : ви можете мати ланцюги всередині пакету або пакети як елементи більшого ланцюга. Це особливо корисно, коли ви хочете, щоб кілька ланцюгів працювали "паралельно" та запускали щось після їх завершення.
Виконання, пріоритети та розгортання працівників
Вся магія черг відбувається тому, що у вас є один або декілька працівників запуск команди queue:work (o queue:listen(хоча останній варіант менш ефективний). Ці процеси є довготривалими демонами, які прослуховують відповідні черги.
Ключові опції queue:work:
- Зв'язок:
php artisan queue:work redis,queue:work database, І т.д. - Кола:
--queue=high,default,lowрозставити пріоритети. - – один раз: обробляє одне завдання та завершує роботу.
- –max-jobs: обробляє X завдань та завершує роботу (для періодичного перезапуску виконавців та звільнення пам'яті).
- –максимальний час: обробляється протягом N секунд, а потім завершується.
- –сон: секунди очікування, коли немає доступних завдань.
- –зупинка-коли-порожньо: очищує чергу та коректно закривається (ідеально підходить для середовищ Docker або одноразових завдань).
- -час вийшов: секунди тайм-ауту на завдання, як ми вже згадували раніше.
- - сила: обробляти завдання, навіть якщо програма перебуває в режимі обслуговування.
Оскільки ці процеси виконуються тривало, у продакшені майже завжди використовується монітор процесів, такий як Supervisor (у Linux), для запуску X-воркерів, їх перезапуску у разі збою та керування журналами. Типовий блок конфігурації Supervisor для Laravel визначає:
- команда з
php artisan queue:work ...бетон. - numprocs щоб вказати, скільки воркерів запускати паралельно.
- Шляхи журналів, користувач, автоматичні перезапуски тощо.
Призупинення та відновлення черг у Laravel
Давайте перейдемо до того, що часто важливо в обслуговуванні: як призупинити черги працівників, не зупиняючи їх різко та не залишаючи завдання незавершеними. Laravel пропонує кілька механізмів, деякі новіші, деякі класичні, для керування цим.
Пауза: черга: сигнали паузи та переривання
В останніх версіях Laravel додано спеціальні команди для призупинення та відновлення виконання воркерів:
php artisan queue:pause: повідомляє системі про необхідність припинити виконання нових завдань, але дозволити тим, що вже виконуються, завершитися.php artisan queue:continue: відновлює обробку нових завдань з того місця, де вона була зупинена.
Ці команди працюють, надсилаючи сигнали до воркерів через механізм кешування, який Laravel використовує внутрішньо. Ось чому важливо мати правильно налаштований драйвер кешу (memcached, redis, database, file тощо), навіть якщо ваша фактична черга кешу — це щось інше.
Важливо: Призупинення черги або працівника не скасовує та не видаляє завдання, які вже перебувають у черзі ; це просто запобігає видаленню інших завдань із черги, поки триває пауза.
Опитування щодо збоїв та їх впливу на продуктивність
Щоразу, коли воркер завершує завдання, Laravel перевіряє кеш на наявність сигналів перезапуску, паузи або продовження . Це має незначні витрати, але існує. Якщо ви надзвичайно зосереджені на продуктивності та знаєте, що вам не потрібно буде призупиняти або перезапускати воркери з команд, ви можете вимкнути це опитування, викликавши:
Queue::withoutInterruptionPolling();
Або навіть налаштувати статичні властивості Illuminate\Queue\Worker щоб вимкнути певні дотики ($перезавантажувальний, $призупиненоОднак, якщо ви це зробите, а потім спробуєте використати queue:pause o queue:restartробітники Вони не дізнаються.Це дизайнерське рішення, яке слід ретельно обміркувати.
Призупинення вручну за допомогою Supervisor або Docker
Окрім власних команд Laravel , у виробничому середовищі процеси часто опосередковано "призупиняються":
- Зупинка програми в Supervisor (
supervisorctl stop laravel-worker:*), що вбиває або зупиняє працівників. - Використовуючи –зупинка-коли-порожньо у розгортаннях, щоб дозволити працівникам завершити те, що знаходиться в черзі, а потім завершити роботу.
- У Docker, дозволяючи контейнеру обробляти за допомогою
--stop-when-emptyа потім воно вимикається.
Різниця з queue:pause Повністю зупинивши процеси, ви зупините обробку завдань, доки робочі процеси не перезапустяться. внутрішня пауза Ви можете зберігати процеси в пам'яті, чекаючи на команду continueЦе корисно для швидкого відкату розгортань або коротких періодів обслуговування.
Транзакції бази даних та after_commit
Класична помилка: диспетчеризація завдань всередині транзакцій бази даних . Якщо ви не будете обережні, виконавець може виконати завдання до того, як буде зроблено коміт, а модель або дані, які ви очікуєте, можуть ще не бути в базі даних.
Щоб уникнути цього танцю, у вас є два варіанти:
- активувати
'after_commit' => trueу конфігурації підключення черги. Таким чином, будь-яке завдання, відправлене в межах транзакції, відкладається внутрішньо до його підтвердження. - Використовуйте методи для кожного завдання
afterCommit()obeforeCommit()під час диспетчеризації, щоб примусово застосовувати таку поведінку лише до певних завдань (Jobs).
Якщо транзакцію скасовано, завдання, відправлені під час неї, відкидаються , навіть не потрапляючи до черги, що зазвичай і є бажаним для підтримки узгодженості.
Моніторинг, очищення черги та тестування
Щойно ви запустите черги та запустите їх по-справжньому, недостатньо просто викинути завдання та забути про них; слід стежити та тестувати, як усе поводиться.
Корисні інструменти та команди:
queue:clearочистити певну чергу (SQS, Redis, база даних).queue:monitorвідстежувати кількість завдань у певних чергах та запускати подіюQueueBusyякщо вони перевищують певний поріг.- Події роботи як
Queue::before,Queue::after,Queue::loopingyQueue::failingпідключити користувацьку логіку (логи, метрики, відкати тощо). - Підробки в тестуванні використання
Queue::fake()yBus::fake()щоб перевірити, чи завдання, рядки або пакети були відправлені без їх фактичного виконання.
У тестах ви можете вказати такі речі, як «це завдання було поставлено в чергу X разів» , «ланцюжок із цими завданнями було відправлено в цьому порядку» або «жодне конкретне завдання не було поставлено в чергу», що забезпечує значну безпеку у складних потоках.
З усією цією екосистемою — драйверами, такими як Redis або SQS, добре розробленими завданнями, проміжним програмним забезпеченням для точного налаштування, пакетами, ланцюжками, конфігурацією повторних спроб, керованими супервайзером працівниками та командами, такими як queue:pause y queue:continue— Laravel дозволяє вам опанувати фонову обробку до міліметра, від типової розсилки електронної пошти до розподілених архітектур черг з перемиканням на резервний рівень, гарантуючи, що ваш застосунок продовжуватиме швидко реагувати на рівні HTTP, поки під ним виконується важка робота, не блокуючи користувача.