Очереди в Laravel: Redis Queue, Horizon и Supervisor
Не всё, что делает приложение, должно происходить во время HTTP-запроса. Отправка email, генерация PDF, дёрганье внешнего API, рассылка пуш-уведомлений — это секунды и минуты. Заставлять пользователя ждать ответа всё это время — плохая идея.
Зачем нужны очереди
Очередь — это «отложенная работа». Контроллер кладёт задачу в очередь и сразу возвращает ответ пользователю. Отдельный процесс-воркер забирает задачу и выполняет её в фоне.
Что это даёт:
- Быстрые ответы. HTTP-запрос завершился за 50 мс, а тяжёлая работа продолжается в фоне.
- Устойчивость. Если внешний API упал — задача попробует выполниться позже.
- Масштабирование. Воркеров можно запускать сколько нужно, независимо от веб-сервера.
Стек в Laravel
Redis — брокер. Хранит очередь задач. Быстрый, надёжный, хорошо подходит для большинства сценариев.
Laravel Horizon — дашборд и менеджер воркеров. Показывает throughput, провалившиеся задачи, время выполнения. Управляет тем, сколько процессов держать на каждой очереди.
Supervisor — системный демон Linux. Следит, чтобы процесс воркера всегда был запущен. Воркер упал — Supervisor поднял.
Связка простая: Supervisor запускает Horizon, Horizon управляет воркерами, воркеры читают задачи из Redis.
Простой job
class SendWelcomeEmail implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public int $backoff = 30;
public function __construct(private int $userId) {}
public function handle(Mailer $mailer): void
{
$user = User::findOrFail($this->userId);
$mailer->to($user->email)->send(new WelcomeMail($user));
}
}
// диспатч
SendWelcomeEmail::dispatch($user->id);
Заметьте: в job передаём id, а не саму модель. Между диспатчем и выполнением могут пройти минуты — за это время данные могут измениться.
Несколько очередей
Не сваливайте всё в одну очередь. Разные задачи — разная критичность и время выполнения.
SendSms::dispatch($payload)->onQueue('sms');
GenerateReport::dispatch($id)->onQueue('reports');
В Horizon настраивается отдельно: для sms — 10 воркеров и низкий timeout, для reports — 2 воркера с большим timeout.
Что обязательно учесть
- Идемпотентность. Job может выполниться дважды (повторная попытка после таймаута). Код должен быть готов к этому.
- Retry с backoff. Не долбить внешний API в цикле — давать паузу между попытками.
- Failed jobs. Настройте мониторинг провалившихся задач, иначе они тихо умрут.
- Перезапуск воркеров после деплоя.
php artisan horizon:terminate— иначе старый код будет жить в памяти воркеров.
Запомнить
- Очередь нужна для всего, что не обязано выполниться в HTTP-запросе.
- Redis + Horizon + Supervisor — рабочая комбинация для большинства проектов.
- Передавайте в job идентификаторы, а не модели.
- Идемпотентность и retry — не опция, а требование.
Также может быть интересно
REST API: проектируем контракт, а не эндпоинты
Чем хороший API отличается от плохого, как версионировать и почему контракт важнее реализации.
Читать далее →