Laravel Horizon для PHP: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Laravel Horizon: Мощный мониторинг и управление очередями Redis в PHP

В современной веб-разработке на PHP асинхронная обработка задач через очереди стала стандартом де-факто. Laravel предоставляет встроенную поддержку очередей, но когда проект вырастает, управлять десятками воркеров, отслеживать их производительность и диагностировать проблемы становится сложно. Именно здесь на помощь приходит Laravel Horizon — мощный инструмент для мониторинга и управления очередями Redis.

Horizon — это не просто панель мониторинга. Это полноценная система управления очередями, которая предоставляет красивый веб-интерфейс для отслеживания всех аспектов работы очередей: количество обработанных задач, время выполнения, количество ошибок, статус воркеров и многое другое. В отличие от стандартного подхода, где вы запускаете воркеры вручную, Horizon управляет ими автоматически, распределяя нагрузку между несколькими процессами и балансируя их в зависимости от текущей загрузки.

Ключевое преимущество Horizon — это интеграция с Redis. Он использует Redis не только как драйвер очереди, но и как хранилище для метрик и состояния воркеров. Это делает его невероятно быстрым и масштабируемым. Вы можете запускать Horizon на нескольких серверах, и он будет собирать данные со всех воркеров в единую панель управления. Это идеальное решение для продакшн-среды, где важна каждая миллисекунда.

Установка

Установка Horizon выполняется через Composer — стандартный менеджер пакетов для PHP. Убедитесь, что у вас установлен Composer и ваш проект использует Laravel 5.5+.

composer require laravel/horizon

После установки пакета необходимо опубликовать его конфигурационные файлы и ассеты:

php artisan horizon:install

Эта команда создаст файл конфигурации config/horizon.php и зарегистрирует сервис-провайдер Horizon. Также будет создан файл маршрутов routes/horizon.php, если вы используете Laravel 8+.

Далее необходимо добавить провайдера в config/app.php (если он не был добавлен автоматически):

'providers' => [
    // Другие провайдеры...
    App\Providers\HorizonServiceProvider::class,
],

После этого нужно запустить миграции для создания таблиц, необходимых для работы Horizon (хотя основная логика использует Redis, некоторые данные хранятся в базе данных):

php artisan migrate

И наконец, необходимо настроить Redis. Убедитесь, что у вас установлен и запущен сервер Redis, а в файле .env указаны правильные параметры подключения:

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Быстрый старт: минимальный рабочий пример

После установки и настройки вы можете сразу запустить Horizon и увидеть его в действии.

Шаг 1: Запустите Horizon

В терминале выполните команду:

php artisan horizon

Вы увидите, как Horizon запускает несколько процессов-воркеров. По умолчанию он создает три процесса для обработки задач из очереди default.

Шаг 2: Создайте задачу для очереди

Создадим простую задачу, которая будет имитировать длительную операцию:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessPodcast implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $podcastId;

    public function __construct($podcastId)
    {
        $this->podcastId = $podcastId;
    }

    public function handle()
    {
        // Имитация обработки
        sleep(3);
        \Log::info('Podcast processed: ' . $this->podcastId);
    }
}

Шаг 3: Отправьте задачу в очередь

В любом месте вашего приложения (например, в контроллере) выполните:

ProcessPodcast::dispatch(42);

Шаг 4: Откройте панель управления

Перейдите по адресу http://your-app.com/horizon. Вы увидите дашборд, на котором отображается информация о запущенных воркерах, обработанных задачах, времени выполнения и ошибках. Если вы отправили задачу, она должна появиться в списке обработанных задач.

Основные методы и классы

Laravel Horizon предоставляет несколько ключевых классов и методов для управления очередями. Рассмотрим 8 наиболее важных из них.

1. Horizon::auth()

Сигнатура: Horizon::auth(callable $callback)

Описание: Определяет, может ли пользователь получить доступ к панели управления Horizon. По умолчанию доступ разрешен только в локальной среде (APP_ENV=local).

// В App\Providers\HorizonServiceProvider::gate()
Horizon::auth(function ($request) {
    // Разрешаем доступ только администраторам
    return auth()->user() && auth()->user()->isAdmin();
});

2. Horizon::routeMailNotificationsTo()

Сигнатура: Horizon::routeMailNotificationsTo(string $email)

Описание: Указывает email-адрес, на который будут отправляться уведомления о проблемах с очередями (например, когда время ожидания задачи превышает лимит).

// В App\Providers\HorizonServiceProvider::boot()
Horizon::routeMailNotificationsTo('admin@example.com');

3. Horizon::routeSlackNotificationsTo()

Сигнатура: Horizon::routeSlackNotificationsTo(string $url, string $channel = null)

Описание: Настраивает отправку уведомлений в Slack-канал при возникновении проблем с очередями.

Horizon::routeSlackNotificationsTo('https://hooks.slack.com/services/...', '#ops');

4. Horizon::night()

Сигнатура: Horizon::night()

Описание: Включает "ночной режим" для панели управления Horizon, делая её тёмной. Это удобно для работы в условиях низкой освещённости.

// В App\Providers\HorizonServiceProvider::boot()
Horizon::night();

5. Horizon::use()

Сигнатура: Horizon::use(string $environment, array $config)

Описание: Определяет конфигурацию воркеров для конкретного окружения (например, local, production). Позволяет гибко настраивать количество воркеров и их приоритеты.

// В config/horizon.php
'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default', 'high'],
            'balance' => 'auto',
            'processes' => 10,
            'tries' => 3,
        ],
    ],
    'local' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default'],
            'balance' => 'simple',
            'processes' => 3,
            'tries' => 3,
        ],
    ],
],

6. Horizon::pause() и Horizon::continue()

Сигнатура: Horizon::pause() и Horizon::continue()

Описание: Приостанавливает и возобновляет обработку всех очередей. Полезно для обслуживания сервера или при деплое.

// Приостановить обработку
Horizon::pause();

// Возобновить обработку
Horizon::continue();

7. Horizon::terminate()

Сигнатура: Horizon::terminate()

Описание: Мягко завершает работу всех воркеров Horizon. Текущие задачи будут завершены, но новые не будут приняты.

// Завершить работу Horizon
Horizon::terminate();

8. Horizon::check()

Сигнатура: Horizon::check()

Описание: Проверяет, запущен ли в данный момент мастер-процесс Horizon. Возвращает true или false.

if (Horizon::check()) {
    echo "Horizon is running!";
} else {
    echo "Horizon is not running!";
}

Реальный практический пример

Давайте создадим полноценную систему обработки заказов с использованием Horizon. Предположим, у нас есть интернет-магазин, и после оформления заказа нужно выполнить несколько действий: отправить email, обновить складские остатки и создать задачу для службы доставки.

Шаг 1: Создаем задачу для обработки заказа

<?php

namespace App\Jobs;

use App\Models\Order;
use App\Services\DeliveryService;
use App\Services\InventoryService;
use App\Services\NotificationService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;

class ProcessOrder implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $timeout = 120; // Максимальное время выполнения задачи (120 секунд)
    public $tries = 3; // Количество попыток выполнения
    public $backoff = [5, 10, 30]; // Задержка между попытками (в секундах)

    protected $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    public function handle(
        NotificationService $notificationService,
        InventoryService $inventoryService,
        DeliveryService $deliveryService
    )
    {
        try {
            // Шаг 1: Отправляем email с подтверждением
            Log::info("Processing order {$this->order->id}: Sending confirmation email");
            $notificationService->sendOrderConfirmation($this->order);

            // Шаг 2: Обновляем складские остатки
            Log::info("Processing order {$this->order->id}: Updating inventory");
            foreach ($this->order->items as $item) {
                $inventoryService->decrementStock($item->product_id, $item->quantity);
            }

            // Шаг 3: Создаем задачу для доставки
            Log::info("Processing order {$this->order->id}: Creating delivery task");
            $deliveryService->createDeliveryTask($this->order);

            // Шаг 4: Отмечаем заказ как обработанный
            $this->order->update(['status' => 'processed']);

            Log::info("Order {$this->order->id} processed successfully");
        } catch (\Exception $e) {
            Log::error("Failed to process order {$this->order->id}: " . $e->getMessage());
            
            // Если задача провалилась, отмечаем заказ как требующий внимания
            $this->order->update(['status' => 'failed']);
            
            throw $e; // Повторная попытка
        }
    }

    public function failed(\Throwable $exception)
    {
        // Действия при окончательном провале задачи (после всех попыток)
        Log::error("Order {$this->order->id} permanently failed: " . $exception->getMessage());
        
        // Отправляем уведомление администратору
        NotificationService::notifyAdmin("Order {$this->order->id} failed permanently");
    }
}

Шаг 2: Создаем задачу с высоким приоритетом для срочных заказов

<?php

namespace App\Jobs;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessUrgentOrder implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $queue = 'high'; // Помещаем задачу в очередь с высоким приоритетом

    protected $order;

    public function __construct(Order $order)
    {
        $this->order = $order;
    }

    public function handle()
    {
        // Обработка срочного заказа
        // ...
    }
}

Шаг 3: Настраиваем Horizon для обработки нескольких очередей

// config/horizon.php
'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['high', 'default', 'low'],
            'balance' => 'auto',
            'process                    

Рекомендаций