Laravel Horizon
简介
INFO
在深入了解 Laravel Horizon 之前,应先熟悉 Laravel 的基础队列服务。Horizon 为 Laravel 队列增加了额外功能,若不熟悉 Laravel 提供的基础队列特性,可能会感到困惑。
Laravel Horizon 为 Laravel 驱动的 Redis 队列 提供美观的仪表盘和代码驱动的配置。Horizon 让你轻松监控队列系统的关键指标,例如任务吞吐量、运行时间和失败任务。
使用 Horizon 时,所有队列 worker 配置都存储在单一、简洁的配置文件中。在版本控制的文件中定义 worker 配置,部署时可轻松扩展或修改队列 worker。

安装
WARNING
Laravel Horizon 要求使用 Redis 驱动队列。因此,请确保在应用的 config/queue.php 配置文件中将队列连接设为 redis。
可通过 Composer 将 Horizon 安装到项目中:
composer require laravel/horizon安装 Horizon 后,使用 horizon:install Artisan 命令发布资源:
php artisan horizon:install配置
发布 Horizon 资源后,主配置文件位于 config/horizon.php。该文件用于配置应用的队列 worker 选项。每个配置选项都包含用途说明,请仔细阅读。
WARNING
Horizon 内部使用名为 horizon 的 Redis 连接。该连接名已保留,不应在 database.php 中分配给其他 Redis 连接,也不应作为 horizon.php 中 use 选项的值。
环境
安装后,应首先熟悉 environments 配置选项。该选项是应用运行环境的数组,并为每个环境定义 worker 进程选项。默认包含 production 和 local 环境,也可按需添加更多环境:
'environments' => [
'production' => [
'supervisor-1' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
'local' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],
还可定义通配符环境(*),在没有其他匹配环境时使用:
'environments' => [
// ...
'*' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],
启动 Horizon 时,会使用应用当前运行环境的 worker 进程配置。环境通常由 APP_ENV 环境变量 的值决定。例如,默认 local 环境配置为启动 3 个 worker 进程并自动平衡各队列的 worker 数量;默认 production 环境最多启动 10 个 worker 进程并自动平衡各队列的 worker 数量。
WARNING
请确保 horizon 配置文件的 environments 部分包含计划运行 Horizon 的每个环境的条目。
Supervisor
如 Horizon 默认配置文件所示,每个环境可包含一个或多个「supervisor」。默认配置将其定义为 supervisor-1,但你可以自由命名。每个 supervisor 本质上负责「监管」一组 worker 进程,并在各队列间平衡 worker 进程。
若要在某环境中定义新的 worker 进程组,可添加额外的 supervisor。例如,可为应用使用的特定队列定义不同的平衡策略或 worker 进程数量。
维护模式
应用处于维护模式时,Horizon 不会处理队列任务,除非在 Horizon 配置中将 supervisor 的 force 选项设为 true:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'force' => true,
],
],
],
默认值
Horizon 默认配置中有 defaults 选项,用于指定应用 supervisor 的默认值。这些默认值会合并到各环境的 supervisor 配置中,避免重复定义。
平衡策略
与 Laravel 默认队列系统不同,Horizon 允许你从三种 worker 均衡策略中选择:simple、auto 和 false。simple 策略会在 worker 进程之间均匀分配传入的任务:
'balance' => 'simple',
auto 策略(配置文件的默认值)会根据队列当前负载调整每个队列的 worker 进程数量。例如,若 notifications 队列有 1000 个待处理任务而 render 队列为空,Horizon 会为 notifications 队列分配更多 worker,直到该队列清空。
使用 auto 策略时,你可以定义 minProcesses 和 maxProcesses 配置选项,以控制每个队列的最少进程数,以及 Horizon 整体应扩缩至的最大 worker 进程总数:
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'minProcesses' => 1,
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
'tries' => 3,
],
],
],
autoScalingStrategy 配置值决定 Horizon 是根据清空队列所需的总时间(time 策略)还是根据队列上的任务总数(size 策略)向队列分配更多 worker 进程。
balanceMaxShift 和 balanceCooldown 配置值决定 Horizon 满足 worker 需求时的扩展速度。上述示例中,每 3 秒最多创建或销毁 1 个新进程。可根据应用需求调整这些值。
当 balance 选项设为 false 时,将使用 Laravel 的默认行为,即按配置中列出的顺序处理队列。
仪表盘授权
Horizon 仪表盘可通过 /horizon 路由访问。默认情况下,仅在 local 环境可访问。但在 app/Providers/HorizonServiceProvider.php 中有授权 Gate 定义,用于控制非 local 环境下对 Horizon 的访问。可按需修改此 Gate 以限制访问:
/**
* Register the Horizon gate.
*
* This gate determines who can access Horizon in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewHorizon', function (User $user) {
return in_array($user->email, [
'taylor@laravel.com',
]);
});
}替代认证策略
请记住,Laravel 会自动将已认证用户注入 Gate 闭包。若应用通过 IP 限制等其他方式保护 Horizon,Horizon 用户可能无需「登录」。因此,需将上述 function (User $user) 闭包签名改为 function (User $user = null),以强制 Laravel 不要求认证。
静默任务
有时,你可能不想查看应用或第三方包分派的某些任务。与其让这些任务占用「已完成任务」列表空间,不如将其静默。首先,在应用 horizon 配置文件的 silenced 选项中添加任务类名:
'silenced' => [
App\Jobs\ProcessPodcast::class,
],
或者,要静默的任务可实现 Laravel\Horizon\Contracts\Silenced 接口。实现此接口的任务会自动静默,即使未出现在 silenced 配置数组中:
use Laravel\Horizon\Contracts\Silenced;
class ProcessPodcast implements ShouldQueue, Silenced
{
use Queueable;
// ...
}
升级 Horizon
升级 Horizon 到新主版本时,请仔细阅读升级指南。
运行 Horizon
在应用 config/horizon.php 中配置 supervisor 和 worker 后,可使用 horizon Artisan 命令启动 Horizon。该命令会启动当前环境的所有已配置 worker 进程:
php artisan horizon可使用 horizon:pause 和 horizon:continue Artisan 命令暂停 Horizon 进程并指示其继续处理任务:
php artisan horizon:pause
php artisan horizon:continue也可使用 horizon:pause-supervisor 和 horizon:continue-supervisor Artisan 命令暂停和恢复特定 Horizon supervisor:
php artisan horizon:pause-supervisor supervisor-1
php artisan horizon:continue-supervisor supervisor-1可使用 horizon:status Artisan 命令检查 Horizon 进程当前状态:
php artisan horizon:status可使用 horizon:supervisor-status Artisan 命令检查特定 Horizon supervisor 的当前状态:
php artisan horizon:supervisor-status supervisor-1可使用 horizon:terminate Artisan 命令优雅终止 Horizon 进程。当前正在处理的任务会完成后,Horizon 才会停止执行:
php artisan horizon:terminate部署 Horizon
准备将 Horizon 部署到应用实际服务器时,应配置进程监控器监控 php artisan horizon 命令,并在意外退出时重启。下面会介绍如何安装进程监控器。
部署过程中,应指示 Horizon 进程终止,以便进程监控器重启并加载新代码:
php artisan horizon:terminate安装 Supervisor
Supervisor 是 Linux 操作系统的进程监控器,会在 horizon 进程停止执行时自动重启。在 Ubuntu 上可使用以下命令安装。若使用其他系统,通常可通过系统包管理器安装:
sudo apt-get install supervisorINFO
若自行配置 Supervisor 感到复杂,可考虑使用 Laravel Forge,它会为你的 Laravel 项目自动安装并配置 Supervisor。
Supervisor 配置
Supervisor 配置文件通常位于服务器的 /etc/supervisor/conf.d 目录。可在此目录创建任意数量的配置文件,指示 supervisor 如何监控进程。例如,创建 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定义 Supervisor 配置时,应确保 stopwaitsecs 的值大于最长运行任务所需的秒数。否则,Supervisor 可能在任务完成前将其终止。
WARNING
上述示例适用于 Ubuntu 服务器,但其他服务器操作系统中 Supervisor 配置文件的位置和扩展名可能不同。请参阅服务器文档了解更多信息。
启动 Supervisor
创建配置文件后,可使用以下命令更新 Supervisor 配置并启动受监控的进程:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start horizonINFO
有关运行 Supervisor 的更多信息,请参阅 Supervisor 文档。
标签
Horizon 允许为任务分配「标签」,包括 mailable、广播事件、通知和队列事件监听器。Horizon 会根据任务附带的 Eloquent 模型智能自动标记大多数任务。例如,查看以下任务:
<?php
namespace App\Jobs;
use App\Models\Video;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class RenderVideo implements ShouldQueue
{
use Queueable;
/**
* Create a new job instance.
*/
public function __construct(
public Video $video,
) {}
/**
* Execute the job.
*/
public function handle(): void
{
// ...
}
}若此任务与 id 为 1 的 App\Models\Video 实例一起入队,将自动获得标签 App\Models\Video:1。Horizon 会搜索任务属性中的 Eloquent 模型,若找到则使用模型类名和主键智能标记任务:
use App\Jobs\RenderVideo;
use App\Models\Video;
$video = Video::find(1);
RenderVideo::dispatch($video);
手动标记任务
若要手动定义可队列对象的标签,可在类上定义 tags 方法:
class RenderVideo implements ShouldQueue
{
/**
* Get the tags that should be assigned to the job.
*
* @return array<int, string>
*/
public function tags(): array
{
return ['render', 'video:'.$this->video->id];
}
}
手动标记事件监听器
获取队列事件监听器的标签时,Horizon 会自动将事件实例传递给 tags 方法,以便将事件数据添加到标签中:
class SendRenderNotifications implements ShouldQueue
{
/**
* Get the tags that should be assigned to the listener.
*
* @return array<int, string>
*/
public function tags(VideoRendered $event): array
{
return ['video:'.$event->video->id];
}
}
通知
WARNING
配置 Horizon 发送 Slack 或 SMS 通知时,请查阅相关通知通道的先决条件。
若希望在某个队列等待时间过长时收到通知,可使用 Horizon::routeMailNotificationsTo、Horizon::routeSlackNotificationsTo 和 Horizon::routeSmsNotificationsTo 方法。可在 App\Providers\HorizonServiceProvider 的 boot 方法中调用:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
parent::boot();
Horizon::routeSmsNotificationsTo('15556667777');
Horizon::routeMailNotificationsTo('example@example.com');
Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
}配置通知等待时间阈值
可在应用 config/horizon.php 中配置多少秒视为「长时间等待」。该文件中的 waits 选项控制每个连接/队列组合的长等待阈值。未定义的连接/队列组合默认为 60 秒:
'waits' => [
'redis:critical' => 30,
'redis:default' => 60,
'redis:batch' => 120,
],
指标
Horizon 包含指标仪表盘,提供任务和队列等待时间及吞吐量信息。要填充该仪表盘,应在应用 routes/console.php 中配置 Horizon 的 snapshot Artisan 命令每 5 分钟运行一次:
use Illuminate\Support\Facades\Schedule;
Schedule::command('horizon:snapshot')->everyFiveMinutes();
删除失败任务
若要删除失败任务,可使用 horizon:forget 命令。该命令接受失败任务的 ID 或 UUID 作为唯一参数:
php artisan horizon:forget 5若要删除所有失败任务,可向 horizon:forget 命令提供 --all 选项:
php artisan horizon:forget --all清空队列中的任务
若要删除应用默认队列中的所有任务,可使用 horizon:clear Artisan 命令:
php artisan horizon:clear可提供 queue 选项以删除特定队列中的任务:
php artisan horizon:clear --queue=emails