Laravel Horizon
介绍
NOTE
在深入了解 Laravel Horizon 之前,您应该先熟悉 Laravel 的基本队列服务。Horizon 增强了 Laravel 的队列,提供了额外的功能,如果您不熟悉 Laravel 提供的基本队列功能,可能会感到困惑。
Laravel Horizon 为您的 Laravel 驱动的 Redis 队列 提供了一个美观的仪表盘和代码驱动的配置。Horizon 允许您轻松监控队列系统的关键指标,如作业吞吐量、运行时间和作业失败。
使用 Horizon 时,所有的队列工作者配置都存储在一个简单的配置文件中。通过在版本控制文件中定义应用程序的工作者配置,您可以在部署应用程序时轻松扩展或修改应用程序的队列工作者。

安装
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
。此配置文件允许您配置应用程序的队列工作者选项。每个配置选项都包含其目的的描述,因此请务必彻底探索此文件。
WARNING
Horizon 在内部使用名为 horizon
的 Redis 连接。此 Redis 连接名称是保留的,不应分配给 database.php
配置文件中的其他 Redis 连接或 horizon.php
配置文件中的 use
选项的值。
环境
安装后,您应该熟悉的主要 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
环境变量 的值确定。例如,默认的 local
Horizon 环境配置为启动三个工作者进程,并自动平衡分配给每个队列的工作者进程数量。默认的 production
环境配置为启动最多 10 个工作者进程,并自动平衡分配给每个队列的工作者进程数量。
WARNING
您应该确保 horizon
配置文件的 environments
部分包含您计划运行 Horizon 的每个环境的条目。
监督者
正如您在 Horizon 的默认配置文件中看到的,每个环境可以包含一个或多个“监督者”。默认情况下,配置文件将此监督者定义为 supervisor-1
;但是,您可以随意命名您的监督者。每个监督者本质上负责“监督”一组工作者进程,并负责在队列之间平衡工作者进程。
如果您希望为应用程序使用的某个队列定义不同的平衡策略或工作者进程计数,您可以为给定环境添加额外的监督者。
维护模式
当您的应用程序处于维护模式时,除非在 Horizon 配置文件中将监督者的 force
选项定义为 true
,否则 Horizon 不会处理排队的作业:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'force' => true,
],
],
],
默认值
在 Horizon 的默认配置文件中,您会注意到一个 defaults
配置选项。此配置选项指定应用程序监督者的默认值。监督者的默认配置值将合并到每个环境的监督者配置中,允许您在定义监督者时避免不必要的重复。
仪表板授权
可通过 /horizon
路由访问 Horizon 仪表板。默认情况下,你只能在 local
环境中访问此仪表板。然而,在你的 app/Providers/HorizonServiceProvider.php
文件中,有一个授权 Gate 的定义。该授权 Gate 控制在 非本地 环境中对 Horizon 的访问。你可以按需修改此 Gate,以限制对你的 Horizon 安装的访问:
/**
* 注册 Horizon Gate。
*
* 此 Gate 决定谁可以在非本地环境访问 Horizon。
*/
protected function gate(): void
{
Gate::define('viewHorizon', function (User $user) {
return in_array($user->email, [
'taylor@laravel.com',
]);
});
}
替代身份验证策略
请记住,Laravel 会自动将已认证的用户注入到 Gate 闭包中。如果你的应用通过其他方法(例如 IP 限制)为 Horizon 提供安全性,那么你的 Horizon 用户可能不需要 "login"。因此,你需要将上面的 function (User $user)
闭包签名更改为 function (User $user = null)
,以强制 Laravel 不要求身份验证。
最大任务尝试次数
NOTE
在优化这些选项之前,请确保你熟悉 Laravel 的默认队列服务和 'attempts' 的概念。
你可以在 supervisor 的配置中定义任务可以消耗的最大尝试次数:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'tries' => 10,
],
],
],
NOTE
此选项类似于使用 Artisan 命令处理队列时的 --tries
选项。
在使用 WithoutOverlapping
或 RateLimited
等中间件时,调整 tries
选项是必要的,因为它们会消耗尝试次数。要处理这个问题,请在 supervisor 级别调整 tries
配置值,或在任务类上定义 $tries
属性。
如果你未设置 tries
选项,Horizon 默认只尝试一次,除非作业类定义了 $tries
,该属性的优先级高于 Horizon 的配置。
将 tries
或 $tries
设置为 0 允许无限次尝试,这在尝试次数不确定时是理想的。为了防止无限失败,你可以通过在任务类上设置 $maxExceptions
属性来限制允许的异常数量。
任务超时
同样,你可以在 supervisor 级别设置 timeout
值,它指定工作进程在强制终止之前可以运行任务多少秒。一旦终止,任务将根据你的队列配置被重试或标记为失败:
'environments' => [
'production' => [
'supervisor-1' => [
// ...¨
'timeout' => 60,
],
],
],
WARNING
当使用 auto
平衡策略时,Horizon 会将正在进行的 worker 视为"挂起"状态,并在缩容期间在 Horizon 超时后强制终止它们。始终确保 Horizon 超时时间大于任何作业级别的超时时间,否则作业可能在执行过程中被终止。此外,timeout
值应该始终比 config/queue.php
配置文件中定义的 retry_after
值短几秒。否则,您的作业可能会被处理两次。
任务退避
你可以在 supervisor 级别定义 backoff
值,用于指定当作业遇到未处理的异常时,Horizon 在重试该作业前应等待多长时间:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'backoff' => 10,
],
],
],
你也可以通过为 backoff
值使用数组来配置“指数型”退避。在此示例中,第一次重试延迟为 1 秒,第二次为 5 秒,第三次为 10 秒,如果仍有剩余尝试次数,之后的每次重试都为 10 秒:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'backoff' => [1, 5, 10],
],
],
],
静默作业
有时,您可能不希望查看应用程序或第三方包调度的某些作业。相反,这些作业会占用“已完成作业”列表中的空间,您可以将它们静默。要开始,请将作业的类名添加到应用程序的 horizon
配置文件中的 silenced
配置选项中:
'silenced' => [
App\Jobs\ProcessPodcast::class,
],
或者,您希望静默的作业可以实现 Laravel\Horizon\Contracts\Silenced
接口。如果作业实现了此接口,即使它不在 silenced
配置数组中,它也会自动被静默:
use Laravel\Horizon\Contracts\Silenced;
class ProcessPodcast implements ShouldQueue, Silenced
{
use Queueable;
// ...
}
均衡策略
每个 supervisor 可以处理一个或多个队列,但与 Laravel 的默认队列系统不同,Horizon 允许你在三种工作进程均衡策略之间进行选择:auto
、simple
和 false
。
自动均衡
auto
策略(默认策略)会根据队列的当前工作负载调整每个队列的工作进程数量。例如,如果你的 notifications
队列有 1,000 个待处理任务,而 default
队列为空,Horizon 会为 notifications
队列分配更多的工作进程,直到该队列清空。
使用 auto
策略时,你还可以配置 minProcesses
和 maxProcesses
选项:
minProcesses
定义每个队列的最小工作进程数。该值必须大于或等于 1。maxProcesses
定义 Horizon 在所有队列中可扩容到的工作进程总数上限。该值通常应大于队列数量乘以minProcesses
的值。要阻止 supervisor 派生任何进程,你可以将该值设置为 0。
例如,你可以将 Horizon 配置为每个队列至少保持 1 个进程,并最多扩容到总计 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 不会在队列之间强制严格的优先级。supervisor 配置中队列的顺序不会影响工作进程的分配。相反,Horizon 会依赖所选的 autoScalingStrategy
,根据队列负载动态分配工作进程。
例如,在以下配置中,尽管 high
队列出现在列表首位,它也不会优先于 default
队列:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'queue' => ['high', 'default'],
'minProcesses' => 1,
'maxProcesses' => 10,
],
],
],
如果你需要在队列之间实施相对优先级,可以定义多个 supervisor 并显式分配处理资源:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'queue' => ['default'],
'minProcesses' => 1,
'maxProcesses' => 10,
],
'supervisor-2' => [
// ...
'queue' => ['images'],
'minProcesses' => 1,
'maxProcesses' => 1,
],
],
],
在此示例中,default
队列最多可扩容到 10 个进程,而 images
队列被限制为 1 个进程。此配置确保你的队列可以独立扩缩容。
NOTE
在调度资源密集型作业时,有时最好将它们分配到一个具有受限 maxProcesses
值的专用队列。否则,这些作业可能会过度消耗 CPU 资源并使系统过载。
简单均衡
simple
策略会在指定队列之间平均分配工作进程。使用该策略时,Horizon 不会自动扩缩工作进程数量,而是使用固定数量的进程:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'queue' => ['default', 'notifications'],
'balance' => 'simple',
'processes' => 10,
],
],
],
在上述示例中,Horizon 会为每个队列分配 5 个进程,将总计 10 个进程平均分配。
如果你希望分别控制分配给每个队列的工作进程数量,你可以定义多个 supervisor:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'queue' => ['default'],
'balance' => 'simple',
'processes' => 10,
],
'supervisor-notifications' => [
// ...
'queue' => ['notifications'],
'balance' => 'simple',
'processes' => 2,
],
],
],
通过此配置,Horizon 将为 default
队列分配 10 个进程,为 notifications
队列分配 2 个进程。
无均衡
当 balance
选项设置为 false
时,Horizon 会严格按照队列在列表中的顺序进行处理,类似于 Laravel 的默认队列系统。然而,如果作业开始积压,它仍会按需扩缩工作进程的数量:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'queue' => ['default', 'notifications'],
'balance' => false,
'minProcesses' => 1,
'maxProcesses' => 10,
],
],
],
在上述示例中,default
队列中的作业始终优先于 notifications
队列中的作业。例如,如果 default
中有 1,000 个作业,而 notifications
中只有 10 个,Horizon 会在处理任何 notifications
作业之前先完全处理完所有 default
作业。
你可以使用 minProcesses
和 maxProcesses
选项来控制 Horizon 扩容工作进程的能力:
minProcesses
定义工作进程的最小总数。该值必须大于或等于 1。maxProcesses
定义 Horizon 可扩容到的工作进程总数上限。
升级 Horizon
在升级到 Horizon 的新主要版本时,您需要仔细查看升级指南。
运行 Horizon
一旦您在应用程序的 config/horizon.php
配置文件中配置了监督者和工作者,您可以使用 horizon
Artisan 命令启动 Horizon。此单个命令将启动当前环境的所有配置的工作者进程:
php artisan horizon
您可以暂停 Horizon 进程,并使用 horizon:pause
和 horizon:continue
Artisan 命令指示其继续处理作业:
php artisan horizon:pause
php artisan horizon:continue
您还可以使用 horizon:pause-supervisor
和 horizon:continue-supervisor
Artisan 命令暂停和继续特定的 Horizon 监督者:
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 监督者的当前状态:
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 上安装 Supervisor,您可以使用以下命令。如果您不使用 Ubuntu,您可以使用操作系统的包管理器安装 Supervisor:
sudo apt-get install supervisor
NOTE
如果自己配置 Supervisor 听起来很复杂,可以考虑使用 Laravel Cloud,它可以为您的 Laravel 应用程序管理后台进程。
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 horizon
NOTE
有关运行 Supervisor 的更多信息,请查阅 Supervisor 文档。
标签
Horizon 允许您为作业分配"标签",包括邮件、广播事件、通知和队列事件监听器。实际上,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;
/**
* 创建一个新的作业实例。
*/
public function __construct(
public Video $video,
) {}
/**
* 执行作业。
*/
public function handle(): void
{
// ...
}
}
如果此作业使用 id
属性为 1
的 App\Models\Video
实例排队,它将自动接收标签 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];
}
}
通知
WARNING
在配置 Horizon 以发送 Slack 或 SMS 通知时,您应该查看相关通知渠道的先决条件。
如果您希望在某个队列有较长等待时间时收到通知,可以使用 Horizon::routeMailNotificationsTo
、Horizon::routeSlackNotificationsTo
和 Horizon::routeSmsNotificationsTo
方法。您可以从应用程序的 App\Providers\HorizonServiceProvider
的 boot
方法中调用这些方法:
/**
* 启动任何应用程序服务。
*/
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,
],
将队列的阈值设置为 0
将禁用该队列的长等待通知。
指标
Horizon 包含一个指标仪表盘,提供有关作业和队列等待时间和吞吐量的信息。为了填充此仪表盘,您应该在应用程序的 routes/console.php
文件中配置 Horizon 的 snapshot
Artisan 命令每五分钟运行一次:
use Illuminate\Support\Facades\Schedule;
Schedule::command('horizon:snapshot')->everyFiveMinutes();
如果你想删除所有的指标数据,可以执行 horizon:clear-metrics
Artisan 命令:
php artisan horizon:clear-metrics
删除失败的作业
如果您希望删除失败的作业,可以使用 horizon:forget
命令。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