Новая PHP-конференция Пых.конф’25 — уже 19 сентября!
Будьте в курсе последних новостей!
Будьте в курсе последних новостей!

Laravel Horizon

Вступление

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

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

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

horizon-example.png

Установка

Laravel Horizon требует, чтобы вы использовали Redis для управления очередью. Следовательно, вы должны убедиться, что соединение с очередью настроено на redis в файле конфигурации приложения config/queue.php.

Вы можете установить Horizon в свой проект с помощью диспетчера пакетов Composer:

composer require laravel/horizon

После установки Horizon опубликуйте его ресурсы с помощью Artisan-команды horizon:install:

php artisan horizon:install

Настройка

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

Horizon использует Redis-соединение с именем «horizon». Это имя соединения Redis зарезервировано и не должно назначаться другому Redis-соединению в файле конфигурации database.php или в качестве значения параметра use в файле конфигурации horizon.php.

Окружение

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

'environments' => [
    'production' => [
        'supervisor-1' => [
            'maxProcesses' => 10,
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
        ],
    ],

    'local' => [
        'supervisor-1' => [
            'maxProcesses' => 3,
        ],
    ],
],

Вы также можете определить среду с подстановочными знаками (*), которая будет использоваться, когда не будет обнаружено другой подходящей среды:

'environments' => [
    // ...

    '*' => [
        'supervisor-1' => [
            'maxProcesses' => 3,
        ],
    ],
],

Когда вы запускаете Horizon, он будет использовать параметры конфигурации рабочего процесса для среды, в которой работает ваше приложение. Как правило, среда определяется значением APP_ENV переменной среды. Например, стандартная локальная среда Horizon настроена на запуск трех рабочих процессов и автоматическое выравнивание количества рабочих процессов, назначенных каждой очереди. По умолчанию рабочая среда настроена на запуск максимум 10 рабочих процессов и автоматический баланс количества рабочих процессов, назначенных каждой очереди.

Вы должны убедиться, что раздел environment файла конфигурации horizon содержит запись для каждой среды, в которой вы планируете запускать Horizon.

Supervisors (Наблюдатели)

Как вы можете увидеть в файле конфигурации Horizon по умолчанию — каждая среда может содержать один или несколько “supervisors” (наблюдателей). По умолчанию в файле конфигурации этот supervisor определяется как supervisor-1; однако вы можете называть своих supervisors как хотите. Каждый supervisor, по сути, отвечает за “наблюдение” за группой рабочих процессов и заботится о балансировке рабочих процессов по очередям.

Вы можете добавить дополнительных “supervisors” в данную среду, если хотите определить новую группу рабочих процессов, которые должны выполняться в этой среде. Вы можете сделать это, если хотите определить другую стратегию балансировки или количество рабочих процессов для данной очереди, используемой вашим приложением.

Режим Обслуживания

Во время работы вашего приложения в режиме обслуживания, отложенные задания не будут обрабатываться Horizon, если опция force для supervisor не определена как true в файле конфигурации Horizon:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'force' => true,
        ],
    ],
],

Значения по умолчанию

В файле конфигурации Horizon по умолчанию вы заметите параметр конфигурации defaults. Эта опция конфигурации определяет значения по умолчанию для Supervisors приложения. Значения конфигурации супервизора по умолчанию будут объединены с конфигурацией супервизора для каждой среды, что позволит вам избежать ненужного повторения при определении супервизоров.

Авторизация в информационной панели (dashboard)

Доступ к информационной панели Horizon можно получить по маршруту /horizon. По умолчанию вы сможете получить доступ к этой панели инструментов только в локальной среде. Однако в файле app/Providers/HorizonServiceProvider.php есть определение шлюза авторизации. Этот шлюз контролирует доступ к Horizon во внешних средах. Вы можете настроить этот шлюз по мере необходимости, чтобы ограничить доступ к вашему приложению Horizon:

/**
 * Регистрация шлюза Horizon.
 *
 * Этот шлюз определяют, кто может получить доступ к Horizon во внешней среде.
 */
protected function gate(): void
{
    Gate::define('viewHorizon', function (User $user) {
        return in_array($user->email, [
            'taylor@laravel.com',
        ]);
    });
}

Альтернативные стратегии аутентификации

Помните, что Laravel автоматически добавляет к шлюзу аутентифицированного пользователя замыкание (closure). Если ваше приложение обеспечивает безопасность Horizon с помощью другого метода, например ограничения IP-адресов, то пользователям Horizon может и не требоваться “аутентификация”. Следовательно, нужно будет изменить написание функции (сигнатуру) выше с function (User $user) на function (User $user = null), чтобы Laravel не требовал аутентификации.

Максимальное количество попыток выполнения задания

Прежде чем настраивать эти параметры, убедитесь, что вы знакомы со стандартными сервисами очередей Laravel и концепцией «попыток».

Вы можете определить максимальное количество попыток, которые может выполнить задание, в настройках супервизора:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'tries' => 10,
        ],
    ],
],

Этот параметр аналогичен параметру --tries при использовании команды Artisan для обработки очередей.

Настройка параметра tries важна при использовании промежуточных программ, таких как WithoutOverlapping или RateLimited, поскольку они потребляют попытки. Для решения этой проблемы измените значение конфигурации tries либо на уровне супервизора, либо определив свойство $tries в классе задания.

Если параметр tries не задан, Horizon по умолчанию использует одну попытку, если только класс задания не определяет $tries, который имеет приоритет над конфигурацией Horizon.

Установка tries или $tries в 0 допускает неограниченное количество попыток, что идеально подходит, когда количество попыток неизвестно. Чтобы предотвратить бесконечные сбои, можно ограничить количество разрешенных исключений, установив свойство $maxExceptions в классе задания.

Тайм-аут задания

Аналогичным образом, вы можете задать значение timeout на уровне супервизора, которое определяет, сколько секунд рабочий процесс может выполнять задание, прежде чем оно будет принудительно завершено. После завершения задание будет либо выполнено повторно, либо помечено как неудавшееся, в зависимости от конфигурации очереди:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...¨
            'timeout' => 60,
        ],
    ],
],

Значение timeout всегда должно быть как минимум на несколько секунд короче значения retry_after, указанного в файле конфигурации config/queue.php. В противном случае ваши задания могут быть обработаны дважды.

Отсрочка задания

Вы можете определить значение backoff на уровне супервизора, чтобы указать, как долго Horizon должен ждать, прежде чем повторить попытку выполнения задания, которое столкнулось с необработанным исключением:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'backoff' => 10,
        ],
    ],
],

Вы также можете настроить «экспоненциальные» задержки, используя массив для значения backoff. В этом примере задержка между попытками составит 1 секунду для первой попытки, 5 секунд для второй, 10 секунд для третьей и 10 секунд для каждой последующей попытки, если остались ещё попытки:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'backoff' => [1, 5, 10],
        ],
    ],
],

Заглушить задания

Иногда вам может быть неинтересно просматривать определенные задания, отправленные вашим приложением или сторонними пакетами. Вместо того чтобы эти задания занимали место в вашем списке “Завершенных заданий”, вы можете заглушить их. Для начала добавьте имя класса задания в параметр конфигурации silenced в файле конфигурации horizon вашего приложения:

'silenced' => [
    App\Jobs\ProcessPodcast::class,
],

В качестве альтернативы задание, которое вы хотите заглушить, может реализовать интерфейс Laravel\Horizon\Contracts\Silenced. Если задание реализует этот интерфейс, оно будет автоматически заглушено, даже если оно отсутствует в массиве конфигурации silenced:

use Laravel\Horizon\Contracts\Silenced;

class ProcessPodcast implements ShouldQueue, Silenced
{
    use Queueable;

    // ...
}

Стратегии балансировки

Каждый супервизор может обрабатывать одну или несколько очередей, но в отличие от стандартной системы очередей Laravel, Horizon позволяет выбирать из трех стратегий балансировки обработчиков: auto, simple и false.

Автоматическая балансировка

Стратегия auto, используемая по умолчанию, корректирует количество рабочих процессов в очереди в зависимости от текущей нагрузки. Например, если в очереди notifications 1000 ожидающих заданий, а очередь default пуста, Horizon будет выделять дополнительные рабочие процессы в очередь notifications, пока она не опустеет.

При использовании стратегии auto вы также можете настроить параметры конфигурации minProcesses и maxProcesses:

  • minProcesses определяет минимальное количество рабочих процессов в очереди. Это значение должно быть больше или равно 1.
  • maxProcesses определяет максимальное общее количество рабочих процессов, до которого Horizon может масштабироваться по всем очередям. Это значение обычно должно быть больше количества очередей, умноженного на значение minProcesses. Чтобы предотвратить запуск процессов супервизором, можно установить это значение равным 0.

Например, вы можете настроить Horizon для поддержки по крайней мере одного процесса на очередь и масштабировать в общей сложности до 10 рабочих процессов:

'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default', 'notifications'],
            'balance' => 'auto',
            'autoScalingStrategy' => 'time',
            'minProcesses' => 1,
            'maxProcesses' => 10,
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
        ],
    ],
],

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

  • Стратегия time будет назначать работников на основе общего расчетного времени, необходимого для очистки очереди.
  • Стратегия size будет назначать работников на основе общего количества заданий в очереди.

Конфигурационные значения balanceMaxShift и balanceCooldown определяют скорость масштабирования Horizon для удовлетворения потребностей рабочих процессов. В приведенном выше примере каждые три секунды будет создаваться или удаляться не более одного нового процесса. Вы можете изменять эти значения в соответствии с потребностями вашего приложения.

Приоритеты очереди и автоматическая балансировка

При использовании стратегии балансировки auto Horizon не устанавливает строгий приоритет между очередями. Порядок очередей в конфигурации супервизора не влияет на назначение рабочих процессов. Вместо этого Horizon использует выбранную стратегию autoScalingStrategy для динамического распределения рабочих процессов в зависимости от нагрузки на очередь.

Например, в следующей конфигурации высокая очередь не имеет приоритета над очередью по умолчанию, несмотря на то, что она стоит первой в списке:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['high', 'default'],
            'minProcesses' => 1,
            'maxProcesses' => 10,
        ],
    ],
],

Если вам необходимо обеспечить относительный приоритет между очередями, вы можете определить несколько супервизоров и явно выделить ресурсы обработки:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['default'],
            'minProcesses' => 1,
            'maxProcesses' => 10,
        ],
        'supervisor-2' => [
            // ...
            'queue' => ['images'],
            'minProcesses' => 1,
            'maxProcesses' => 1,
        ],
    ],
],

В этом примере очередь по умолчанию queue может масштабироваться до 10 процессов, тогда как очередь images ограничена одним процессом. Такая конфигурация гарантирует независимое масштабирование очередей.

При диспетчеризации ресурсоёмких заданий иногда лучше назначать их в отдельную очередь с ограниченным значением maxProcesses. В противном случае эти задания могут потреблять чрезмерное количество ресурсов ЦП и перегружать систему.

Простая балансировка

Стратегия simple равномерно распределяет рабочие процессы по указанным очередям. При использовании этой стратегии Horizon не масштабирует количество рабочих процессов автоматически. Вместо этого используется фиксированное количество процессов:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['default', 'notifications'],
            'balance' => 'simple',
            'processes' => 10,
        ],
    ],
],

В приведенном выше примере Horizon назначит 5 процессов каждой очереди, разделив общее количество процессов (10) поровну.

Если вы хотите контролировать количество рабочих процессов, назначенных каждой очереди индивидуально, вы можете определить несколько супервизоров:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['default'],
            'balance' => 'simple',
            'processes' => 10,
        ],
        'supervisor-notifications' => [
            // ...
            'queue' => ['notifications'],
            'balance' => 'simple',
            'processes' => 2,
        ],
    ],
],

При такой конфигурации Horizon назначит 10 процессов в очередь «по умолчанию» и 2 процесса в очередь «уведомлений».

Без балансировки

Если параметр balance установлен в значение false, Horizon обрабатывает очереди строго в порядке их перечисления, аналогично стандартной системе очередей Laravel. Однако он всё равно будет масштабировать количество рабочих процессов, если задания начнут накапливаться:

'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['default', 'notifications'],
            'balance' => false,
            'minProcesses' => 1,
            'maxProcesses' => 10,
        ],
    ],
],

В приведённом выше примере задания в очереди default всегда имеют приоритет над заданиями в очереди notifications. Например, если в очереди default находится 1000 заданий, а в очереди notifications — только 10, Horizon полностью обработает все задания default, прежде чем обрабатывать задания из очереди notifications.

Вы можете контролировать возможности Horizon по масштабированию рабочих процессов с помощью параметров minProcesses и maxProcesses:

  • minProcesses определяет минимальное количество рабочих процессов. Это значение должно быть больше или равно 1.
  • maxProcesses определяет максимальное общее количество рабочих процессов, до которого Horizon может масштабироваться.

Обновление Horizon

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

Запуск Horizon

После того как вы настроили свои супервизоры (supervisors) и рабочие процессы (workers) в файле конфигурации приложения config/horizon.php, вы можете запустить Horizon, используя Artisan-команду horizon. Эта единственная команда запустит все настроенные рабочие процессы для текущей среды:

php artisan horizon

Вы можете приостановить процесс Horizon и дать ему указание продолжить обработку заданий, используя Artisan-команды horizon:pauseи horizon:continue:

php artisan horizon:pause

php artisan horizon:continue

Вы также можете приостановить и продолжить определенные Horizon супервизоры, используя Artisan-команды horizon:pause-supervisor и horizon:continue-supervisor:

php artisan horizon:pause-supervisor supervisor-1

php artisan horizon:continue-supervisor supervisor-1

Вы можете проверить текущий статус процесса Horizon, используя Artisan-команду horizon:status:

php artisan horizon:status

Вы можете проверить текущий статус конкретного супервизора Horizon с помощью Artisan-команды horizon:supervisor-status:

php artisan horizon:supervisor-status supervisor-1

Вы можете корректно завершить процесс Horizon, используя Artisan-команду horizon:terminate. Все задания, которые в настоящее время обрабатываются, будут завершены, а затем Horizon прекратит работу:

php artisan horizon:terminate

Развертывание Horizon (deploy)

Когда вы будете готовы развернуть Horizon на фактическом сервере приложения, вам следует настроить монитор процессов для отслеживания командой php artisan horizon и перезапустить ее, если она неожиданно завершится. Не волнуйтесь, ниже мы обсудим, как установить монитор процессов.

Во время процесса развертывания приложения вы должны дать команду Horizon завершить процесс, чтобы он был перезапущен монитором процессов и получил изменения кода:

php artisan horizon:terminate

Установка Supervisor

Supervisor – это монитор процессов для операционной системы Linux, который автоматически перезапустит ваш процесс horizon, если он перестанет выполняться. Чтобы установить Supervisor в Ubuntu, вы можете использовать следующую команду. Если вы не используете Ubuntu, вы, вероятно, можете установить Supervisor с помощью диспетчера пакетов вашей операционной системы:

sudo apt-get install supervisor

Если настройка Supervisor сама по себе кажется утомительной, рассмотрите возможность использования Laravel Cloud, который может управлять фоновыми процессами для ваших приложений Laravel.

Настройка Supervisor

Файлы конфигурации супервизора обычно хранятся в каталоге вашего сервера /etc/supervisor/conf.d. В этом каталоге вы можете создать любое количество файлов конфигурации, которые определяют для супервизора, как следует контролировать процессы. Например, давайте создадим файл horizon.conf, который запускает и отслеживает процесс horizon:

[program:horizon]
process_name=%(program_name)s
command=php /home/forge/example.com/artisan horizon
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/example.com/horizon.log
stopwaitsecs=3600

При определении конфигурации вашего супервизора вы должны убедиться, что значение stopwaitsecs больше, чем количество секунд, потребляемых вашим самым длительным выполняемым заданием. В противном случае Supervisor может удалить ваш процесс задания до того, как оно завершит обработку.

Хотя приведенные выше примеры являются действительными для серверов на основе Ubuntu, местоположение и расширение файла, ожидаемые для файлов конфигурации супервизора, могут отличаться для других операционных систем серверов. Пожалуйста, обратитесь к документации вашего сервера для получения дополнительной информации.

Запуск Supervisor

После создания файла конфигурации вы можете обновить конфигурацию Supervisor и запустить отслеживаемые процессы, используя следующие команды:

sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start horizon

Для получения дополнительной информации о запуске Supervisor обратитесь к документации Supervisor.

Теги

Horizon позволяет назначать “теги” (tags) заданиям, включая почтовые сообщения, широковещательные события, уведомления и прослушиватели событий в очереди. Фактически, Horizon будет интеллектуально и автоматически помечать большинство заданий в зависимости от моделей Eloquent, прикрепленных к заданию. Например, взгляните на следующее задание (job):

<?php

namespace App\Jobs;

use App\Models\Video;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class RenderVideo implements ShouldQueue
{
    use Queueable;

    /**
     * Создаем новый экземпляр задания.
     */
    public function __construct(
        public Video $video,
    ) {}

    /**
     * Выполнение задания.
     */
    public function handle(): void
    {
        // ...
    }
}

Если это задание поставлено в очередь с экземпляром App\Models\Video с атрибутом id равным 1, то оно автоматически получит тег App\Models\Video:1. Это потому, что Horizon будет искать в свойствах задания любые модели Eloquent. Если модели Eloquent будут найдены, Horizon разумно пометит задание, используя имя класса модели и первичный ключ:

use App\Jobs\RenderVideo;
use App\Models\Video;

$video = Video::find(1);

RenderVideo::dispatch($video);

Самостоятельное тегирование заданий

Если вы хотите самостоятельно определить теги для одного из объектов в очереди, вы можете определить в классе метод “tags()”:

class RenderVideo implements ShouldQueue
{
    /**
     * Получаем теги, которые должны быть назначены заданию.
     *
     * @return array<int, string>
     */
    public function tags(): array
    {
        return ['render', 'video:'.$this->video->id];
    }
}

Самостоятельное тегирование слушателей событий

При получении тегов для слушателя событий в очереди Horizon автоматически передаст экземпляр события методу tags, что позволит вам добавить данные события к тегам:

class SendRenderNotifications implements ShouldQueue
{
    /**
     * Получаем теги, которые должны быть назначены прослушивателю.
     *
     * @return array<int, string>
     */
    public function tags(VideoRendered $event): array
    {
        return ['video:'.$event->video->id];
    }
}

Уведомления

При настройке Horizon для отправки уведомлений Slack или SMS необходимо ознакомиться с предварительными условиями для соответствующего канала уведомлений.

Если вы хотите получать уведомления, когда одна из ваших очередей имеет длительное время ожидания, вы можете использовать методы Horizon::routeMailNotificationsTo, Horizon::routeSlackNotificationsTo и Horizon::routeSmsNotificationsTo. Вы можете вызвать эти методы из метода boot провайдера вашего приложения App\Providers\HorizonServiceProvider:

/**
 * Загрузчик сервисов приложения.
 */
public function boot(): void
{
    parent::boot();

    Horizon::routeSmsNotificationsTo('15556667777');
    Horizon::routeMailNotificationsTo('example@example.com');
    Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
}

Настройка пороговых значений времени ожидания уведомлений

Вы можете настроить, сколько секунд будет считаться “долгим ожиданием” в файле конфигурации Horizon config/horizon.php. Параметр конфигурации waits в этом файле позволяет вам настраивать пороги ожидания для каждой комбинации соединения/очереди. Любый комбинации соединения/очереди, не определённые в waits, по умолчанию будут иметь порог ожидания в 60 секунд:

'waits' => [
    'redis:critical' => 30,
    'redis:default' => 60,
    'redis:batch' => 120,
],

Метрики

Horizon включает в себя панель метрик, которая предоставляет информацию о времени ожидания задач и очереди, а также пропускной способности. Чтобы записывать информацию в эту панель, вы должны настроить Artisan-команду Horizon snapshot для выполнения каждые пять минут в файле routes/console.php вашего приложения:

use Illuminate\Support\Facades\Schedule;

Schedule::command('horizon:snapshot')->everyFiveMinutes();

Если вы хотите удалить все данные метрик, вы можете вызвать команду Artisan horizon:clear-metrics:

php artisan horizon:clear-metrics

Удаление невыполненных заданий

Если вы хотите удалить неудавшееся задание, можете использовать команду horizon:forget. Команда horizon:forget принимает идентификатор или UUID неудачного задания в качестве своего единственного аргумента:

php artisan horizon:forget 5

Если вы хотите удалить все неудачные задания, вы можете указать опцию --all для команды horizon:forget:

php artisan horizon:forget --all

Удаление заданий из очередей

Если вы хотите удалить все задания из очереди приложения по умолчанию, то вы можете сделать это с помощью Artisan-команды horizon:clear:

php artisan horizon:clear

Можно добавить опцию queue для удаления заданий из определенной очереди:

php artisan horizon:clear --queue=emails