Під час більшості деплоїв існує часове вікно, коли код на диску вже змінився, але воркери, що працюють з ним, ще не перезапустилися. Воркер, який резервує завдання в цьому вікні, десеріалізує payload, створений старим кодом, і виконує його через новий код. У більшості випадків нічого не відбувається. Інколи перейменований клас завдання або змінена сигнатура конструктора перетворює його на failed job, який доводиться відтворювати вручну.
Звичайний спосіб вирішення - режим обслуговування (maintenance mode), який зупиняє набагато більше, ніж просто чергу, або queue:restart, що є радше запитом, аніж гарантією, оскільки воркер бачить його лише між завданнями. Laravel 13.25 додає третій варіант: перемикач, який зупиняє кожен воркер на кожному з'єднанні від резервування нової роботи, не торкаючись HTTP-трафіку.
Команди
Для призупинки всіх черг:
php artisan queue:pause --all
Для відновлення:
php artisan queue:resume --all
Аргумент queue в обох командах тепер необов'язковий, тому --all - єдине, що потрібно передати. Без прапорця команди поводяться як раніше і приймають пару connection:queue.
Той самий перемикач доступний у фасаді Queue для deploy-скриптів, написаних на PHP, або для контролера за адмін-панеллю:
use Illuminate\Support\Facades\Queue;
Queue::pauseAll();
// ...
Queue::resumeAll();
Як працює призупинка
Призупинена черга зупиняє воркерів від резервування нових завдань. Процес воркера залишається живим і продовжує цикл, він просто спить замість того, щоб витягувати завдання. Завдання, яке вже обробляється на момент призупинки, виконується до завершення, тому queue:pause --all у deploy-скрипті не перериває нічого на льоту. Продюсери також не постраждали: SomeJob::dispatch() продовжує записувати в Redis або базу даних, і ці завдання чекають там, поки ви не відновите чергу.
Механічно це один ключ кешу, illuminate:queues:paused, записаний через forever(). Воркери вже читають кеш один раз на цикл, щоб перевірити сигнали перезапуску та призупинки окремих черг, і глобальний ключ отримується в тому самому виклику many(), тому це не додає жодних додаткових звернень. QueueManager::isPaused() і getPausedQueues() обидва повідомляють чергу як призупинену, коли встановлено або глобальний ключ, або її власний ключ.
Два незалежні перемикачі
pause() і pauseAll() записують різні ключі, і вони навмисно не знають один про одного. Якщо чергу було призупинено індивідуально, resumeAll() залишає її призупиненою:
Queue::pause('redis', 'imports'); // припаркована, поки ми досліджуємо проблемне завдання
Queue::pauseAll(); // деплой починається
Queue::resumeAll(); // деплой завершується
Queue::isPaused('redis', 'imports'); // true
Це поведінка, яку ви хочете в deploy-скрипті. Хтось припаркував чергу imports навмисно годину тому, і деплой, що виконує resumeAll(), не повинен тихо скасовувати це. Очищення індивідуальної призупинки все ще вимагає queue:resume redis:imports.
Події
Дві події спрацьовують поряд із існуючими подіями для окремих черг QueuePaused і QueueResumed:
use Illuminate\Queue\Events\QueuesPaused;
use Illuminate\Queue\Events\QueuesResumed;
Event::listen(function (QueuesPaused $event) {
Log::warning('All queues paused');
});