Налаштування розподілених Background Jobs (кілька воркерів)
Один воркер — одна точка відмови та обмежена пропускна здатність. Кілька воркерів на кількох серверах — це горизонтальне масштабування обробки та стійкість до падіння окремих вузлів. Реалізація вимагає централізованого брокера, правильної конфігурації та розуміння проблем, які виникають при паралельній обробці.
Архітектура
[App Server 1] [App Server 2] [App Server 3]
↓ ↓ ↓
dispatch dispatch dispatch
↓ ↓ ↓
┌─────────────────────────────┐
│ Redis / RabbitMQ │ ← централізований брокер
└─────────────────────────────┘
↓ ↓ ↓
[Worker 1] [Worker 2] [Worker 3] ← можуть бути на різних серверах
Брокер — єдиний компонент, який має бути доступний усім серверам. Інші вузли не спілкуються напрямую.
Вимоги до брокера
Redis — стандартний вибір для Laravel. Вимагає phpredis або predis. Для високої доступності — Redis Sentinel або Redis Cluster.
RabbitMQ — підходить для складних сценаріїв маршрутизації (fanout, topic exchanges). Laravel підтримує через пакет vladimir-yuldashev/laravel-queue-rabbitmq.
Amazon SQS — управлений сервіс, не потрібно обслуговувати. Підходить при інфраструктурі на AWS.
Мінімальна конфігурація Redis для production — окремий сервер (не shared з основною БД), persistence включена (appendonly yes), maxmemory-policy налаштована.
Конфігурація Laravel для розподілених воркерів
// config/queue.php
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'queue', // окремий Redis-з'єднання для черг
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90, // секунди до повтору зависшої задачі
'block_for' => 5, // блокуючий BLPOP замість polling
'after_commit' => true, // диспетч лише після commit БД
],
],
retry_after — ключовий параметр при розподілених воркерах: якщо воркер упав у процесі завдання, завдання буде знову видимо іншим воркерам через retry_after секунд. Повинен бути більше timeout Job'у.
Горизонтальне масштабування через Horizon
Horizon підтримує запуск на кількох серверах. Кожен сервер запускає свій екземпляр Horizon, вони не координуються напрямично — Redis виступає загальним реєстром.
На кожному сервері запускається однаковий Supervisor-конфіг:
[program:horizon]
command=php /var/www/artisan horizon
autostart=true
autorestart=true
user=www-data
stdout_logfile=/var/log/horizon.log
stopwaitsecs=3600
Horizon автоматично балансує воркери всередину одного сервера. Для балансування між серверами — ручне налаштування числа процесів з урахуванням потужності кожного.
Конкурентний доступ та дедупліцирування
При кількох воркерах одне завдання може бути взято двічі, якщо воркер завис та не звільнив блокування. Механізм Redis LPOP атомарен — завдання береться одним воркером. Але «невидимі» завдання (взяті, але не завершені) повертаються у чергу через retry_after.
Якщо завдання повинно виконуватися строго один раз (idempotency) — перевіряємо це явно:
class ProcessPaymentJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private string $paymentId) {}
public function handle(): void
{
// Розподілена блокування через Redis — лише один воркер обробляє платіж
$lock = Cache::lock("payment:{$this->paymentId}", 120);
if (!$lock->get()) {
// Інший воркер вже обробляє
$this->release(10); // повернути в чергу через 10 секунд
return;
}
try {
$payment = Payment::find($this->paymentId);
// Перевірка ідемпотентності
if ($payment?->status !== 'pending') {
return; // вже оброблено
}
$this->processPayment($payment);
} finally {
$lock->release();
}
}
}
Cache::lock() використовує Redis SET NX PX — атомарна операція, що гарантує, що ровно один воркер отримає блокування.
Розділення воркерів за типом навантаження
На різних серверах можна запускати воркеры для різних черг, якщо завдання вимагають специфічних ресурсів:
[Server: API-1, API-2] → воркери для 'critical', 'default'
[Server: Media-1] → воркери для 'transcoding', 'media'
[Server: Worker-1] → воркери для 'batch', 'reports', 'low'
На медіа-сервері стоїть GPU або потужний CPU для FFmpeg; на API-серверах — швидкі воркери з малим timeout.
Supervisor на Media-сервері:
[program:media-worker]
command=php /var/www/artisan queue:work --queue=transcoding,media --timeout=3600 --max-jobs=1
numprocs=2
autostart=true
autorestart=true
user=www-data
--max-jobs=1 — воркер берем одне завдання та перезапускається (звільняє память після важкої операції).
Graceful shutdown
При деплоєю потрібно дочекатися завершення поточних завдань, не вбиваючи воркери різко:
php artisan queue:restart
Ця команда ставить флаг у Redis — воркери завершать поточне завдання та зупиняться. Supervisor перезапустить їх з новим кодом.
У Supervisor stopwaitsecs повинен бути не менше максимального timeout завдання:
stopwaitsecs=3600 # для сервера з транскодуванням
stopwaitsecs=120 # для стандартних воркерів
Моніторинг розподіленого стану
Horizon агрегує метрики всіх серверів в одному дашборді. Ключові показники:
- Throughput (завдань/хвилину) по кожній черзі
- Wait time — середнє час очікування завдання в черзі
- Runtime — середнє время виконання
- Failed jobs — кількість упалих завдань
Автоматичне масштабування воркерів (якщо інфраструктура на Kubernetes):
# HPA для масштабування подів воркерів за метрикою глибини черги
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: queue-workers
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: queue-worker
minReplicas: 2
maxReplicas: 20
metrics:
- type: External
external:
metric:
name: redis_queue_depth
selector:
matchLabels:
queue: default
target:
type: AverageValue
averageValue: "50" # масштабуємо якщо > 50 завдань на воркер
Кастомна метрика redis_queue_depth експортується через Prometheus Redis Exporter.
RabbitMQ як альтернатива
При необхідності складної маршрутизації (різні типи подій → різні черги, fanout трансляція), RabbitMQ дає більше гнучкості:
// config/queue.php
'rabbitmq' => [
'driver' => 'rabbitmq',
'dsn' => env('RABBITMQ_DSN', 'amqp://user:pass@localhost:5672/'),
'queue' => env('RABBITMQ_QUEUE', 'default'),
'options' => [
'exchange' => [
'name' => 'app-exchange',
'type' => 'direct',
],
'queue' => [
'durable' => true,
'exclusive' => false,
'auto_delete' => false,
],
],
],
RabbitMQ Management UI (порт 15672) надає детальний моніторинг: consumers, connections, channel-навантаження, message rates.
Сроки
Налаштування Redis Sentinel/Cluster або RabbitMQ, конфігурація Horizon на кількох серверах, Supervisor — 1 робочий день. Розподілені блокування, idempotency-перевірки у критичних Job'ах — 6–8 годин. Інтеграція з Kubernetes HPA та Prometheus — окремий проект на 1–2 дні.







