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 Также в библиотеке
Мониторинг приложений C++: инструменты и библиотеки
Elasticsearch клиент для Swift (iOS/macOS): интеграция и примеры
Hibernate ORM: Мощный инструмент для работы с базами данных в Java