Очереди 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.