Разработка/ PHP/ Laravel

Очереди Laravel на Redis: queue:work и Horizon

Что настраиваем

Задача — держать Redis-очереди Laravel постоянно запущенными через Supervisor. Есть два рабочих варианта: обычный queue:work и Horizon. В обоих случаях процесс не должен жить в открытом SSH-окне.

Для маленького проекта я начинаю с queue:work. Horizon беру, когда нужны панель, метрики, failed jobs в удобном виде и управление количеством воркеров из config/horizon.php.

Общий .env

Минимальный набор для Redis-очереди:

QUEUE_CONNECTION=redis
REDIS_QUEUE_CONNECTION=default
REDIS_QUEUE=default
REDIS_QUEUE_RETRY_AFTER=90

REDIS_QUEUE_CONNECTION — Redis-подключение из config/database.php. REDIS_QUEUE — очередь по умолчанию. Если в проекте несколько типов задач, имена лучше задать сразу: high, default, mail, images.

Timeout

--timeout у worker должен быть меньше, чем REDIS_QUEUE_RETRY_AFTER. Например: timeout=60, retry_after=90.

Supervisor: базовые команды

На Ubuntu/Debian Supervisor ставится так:

sudo apt update
sudo apt install supervisor
sudo systemctl enable supervisor
sudo systemctl start supervisor

После каждого нового файла в /etc/supervisor/conf.d:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl status

Если служба называется иначе, сначала смотрю точное имя:

systemctl list-units --type=service | grep -i supervisor

Вариант 1: обычный queue:work

Этот вариант без панели. Supervisor запускает несколько queue:work процессов и поднимает их заново, если они завершились после ошибки, таймаута или queue:restart.

Сначала проверяю команду вручную из каталога проекта:

cd /var/www/shop
/usr/bin/php artisan queue:work redis --queue=high,default --sleep=3 --tries=3 --timeout=60 --max-time=3600

Конфиг Supervisor:

sudo nano /etc/supervisor/conf.d/shop-worker.conf
[program:shop-worker]
process_name=%(program_name)s_%(process_num)02d
command=/usr/bin/php /var/www/shop/artisan queue:work redis --queue=high,default --sleep=3 --tries=3 --timeout=60 --max-time=3600
directory=/var/www/shop
user=www-data
numprocs=2
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
redirect_stderr=true
stdout_logfile=/var/www/shop/storage/logs/supervisor-worker.log
stopwaitsecs=3700

Подключить и запустить:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start shop-worker:*
sudo supervisorctl status shop-worker:*

После деплоя:

php artisan queue:restart

queue:restart просит worker завершиться после текущей задачи. Supervisor увидит остановку процесса и поднимет его уже с новым кодом.

Вариант 2: Horizon

Horizon запускает один главный процесс, а внутри сам управляет воркерами. Поэтому в Supervisor обычно нужен один program:horizon, а количество процессов настраивается в config/horizon.php.

Установка:

composer require laravel/horizon
php artisan horizon:install
php artisan migrate

Пример production-настройки:

'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['high', 'default'],
            'balance' => 'auto',
            'autoScalingStrategy' => 'time',
            'minProcesses' => 1,
            'maxProcesses' => 5,
            'tries' => 3,
            'timeout' => 60,
        ],
    ],
],

Здесь supervisor-1 — имя внутреннего Horizon-supervisor, не Linux Supervisor. Это разные уровни: Linux Supervisor держит команду php artisan horizon, Horizon уже управляет очередями.

Проверка вручную:

cd /var/www/shop
/usr/bin/php artisan horizon

Конфиг Linux Supervisor:

sudo nano /etc/supervisor/conf.d/shop-horizon.conf
[program:shop-horizon]
process_name=%(program_name)s
command=/usr/bin/php /var/www/shop/artisan horizon
directory=/var/www/shop
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/www/shop/storage/logs/horizon.log
stopwaitsecs=3700

Подключить и запустить:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start shop-horizon
sudo supervisorctl status shop-horizon

После деплоя для Horizon использую не queue:restart, а:

php artisan horizon:terminate

Horizon завершит текущие jobs и остановится. Supervisor поднимет процесс снова, уже с обновлённым кодом.

Доступ к /horizon

Панель Horizon живёт на /horizon. На production её нельзя оставлять открытой всем. Минимум — проверить gate доступа:

Gate::define('viewHorizon', function ($user) {
    return in_array($user->email, [
        'admin@example.com',
    ], true);
});

Если в проекте нет публичной авторизации, закрываю /horizon на уровне nginx, basic auth или доступа по VPN.

Что выбрать

Обычный queue:work под Supervisor — нормальный старт: меньше зависимостей, понятный конфиг, простая диагностика через лог worker.

Horizon нужен, когда очередей уже несколько и хочется видеть, где копится нагрузка. Например, images можно ограничить одним процессом, а mail и default дать больше воркеров.

Не смешивать

Не запускайте обычный queue:work и Horizon на одни и те же очереди одновременно. Иначе часть jobs заберёт обычный worker, часть — Horizon, а диагностика станет мутной.

Диагностика

Для обычного worker:

sudo supervisorctl status shop-worker:*
tail -n 100 /var/www/shop/storage/logs/supervisor-worker.log
php artisan queue:failed

Для Horizon:

sudo supervisorctl status shop-horizon
tail -n 100 /var/www/shop/storage/logs/horizon.log
php artisan horizon:status
php artisan horizon:supervisor-status supervisor-1

Если процесс в BACKOFF или FATAL, сначала запускаю команду из Supervisor-конфига вручную под тем же пользователем. Чаще всего проблема в пути к PHP, правах на storage/logs, переменных окружения или слишком коротком stopwaitsecs.

Теги

Читать дальше

Упоминается в