[!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 文件中,有一个授权门定义。此授权门控制在非本地环境中对 Horizon 的访问。您可以根据需要自由修改此门,以限制对 Horizon 安装的访问:
/**
* 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 会自动将已认证的用户注入到门闭包中。如果您的应用程序通过其他方法(例如 IP 限制)提供 Horizon 安全性,那么您的 Horizon 用户可能不需要“登录”。因此,您需要将上面的 function (User $user) 闭包签名更改为 function (User $user = null),以强制 Laravel 不要求认证。
[!NOTE]
在优化这些选项之前,请确保您熟悉 Laravel 的默认队列服务和“尝试次数”的概念。
您可以在监管器的配置中定义任务可以消耗的最大尝试次数:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'tries' => 10,
],
],
],[!NOTE]
此选项类似于使用 Artisan 命令处理队列时的--tries选项。
在使用 WithoutOverlapping 或 RateLimited 等中间件时,调整 tries 选项至关重要,因为它们会消耗尝试次数。要处理此问题,请在监管器级别调整 tries 配置值,或者通过在任务类上定义 $tries 属性来调整。
如果您未设置 tries 选项,Horizon 默认为单次尝试,除非任务类定义了 $tries,该定义优先于 Horizon 配置。
将 tries 或 $tries 设置为 0 允许无限次尝试,这在尝试次数不确定时非常理想。为了防止无休止的失败,您可以通过在任务类上设置 $maxExceptions 属性来限制允许的异常数量。
同样,您可以在监管器级别设置 timeout 值,该值指定工作进程在被强制终止之前可以运行任务的秒数。一旦终止,任务将根据您的队列配置进行重试或标记为失败:
'environments' => [
'production' => [
'supervisor-1' => [
// ...¨
'timeout' => 60,
],
],
],[!WARNING]
当使用auto均衡策略时,Horizon 会将正在进行的工作程序视为“挂起”,并在缩减期间 Horizon 超时后强制终止它们。请始终确保 Horizon 超时时间大于任何任务级别的超时时间,否则任务可能会在执行过程中被终止。此外,timeout值应始终比config/queue.php配置文件中定义的retry_after值至少短几秒。否则,您的任务可能会被处理两次。
您可以在监管器级别定义 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,
],除了静默单个任务类之外,Horizon 还支持根据标签静默任务。如果您想隐藏共享同一标签的多个任务,这会很有用:
'silenced_tags' => [
'notifications'
],或者,您希望静默的任务可以实现 Laravel\Horizon\Contracts\Silenced 接口。如果任务实现了此接口,它将自动被静默,即使它不存在于 silenced 配置数组中:
use Laravel\Horizon\Contracts\Silenced;
class ProcessPodcast implements ShouldQueue, Silenced
{
use Queueable;
// ...
}每个监管器可以处理一个或多个队列,但与 Laravel 的默认队列系统不同,Horizon 允许您选择三种工作程序均衡策略:auto、simple 和 false。
默认策略 auto 会根据队列的当前工作负载调整每个队列的工作进程数量。例如,如果您的 notifications 队列有 1,000 个待处理任务,而您的 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 来根据队列负载动态分配工作进程。
例如,在以下配置中,high 队列不会优先于 default 队列,尽管它在列表中首先出现:
'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 队列限制为一个进程。此配置确保您的队列可以独立扩展。
[!NOTE]
当分派资源密集型任务时,有时最好将它们分配给具有有限maxProcesses值的专用队列。否则,这些任务可能会消耗过多的 CPU 资源并使您的系统过载。
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 将为 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 的新主要版本时,务必仔细查阅升级指南。
在应用程序的 config/horizon.php 配置文件中配置好监管器和工作程序后,您可以使用 horizon Artisan 命令启动 Horizon。此单个命令将启动当前环境的所有已配置工作进程:
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 监管器:
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 部署到应用程序的实际服务器时,您应该配置一个进程监控器来监控 php artisan horizon 命令,并在其意外退出时重新启动它。别担心,我们将在下面讨论如何安装进程监控器。
在应用程序的部署过程中,您应该指示 Horizon 进程终止,以便它将被您的进程监控器重新启动并接收您的代码更改:
php artisan horizon:terminateSupervisor 是一个适用于 Linux 操作系统的进程监控器,如果您的 horizon 进程停止执行,它将自动重新启动。要在 Ubuntu 上安装 Supervisor,您可以使用以下命令。如果您不使用 Ubuntu,您很可能可以使用操作系统的包管理器安装 Supervisor:
sudo apt-get install supervisor[!NOTE]
如果您觉得自行配置 Supervisor 令人生畏,请考虑使用 Laravel Cloud,它可以为您的 Laravel 应用程序管理后台进程。
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 配置并启动受监控的进程:
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;
/**
* 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 模型。如果找到 Eloquent 模型,Horizon 将使用模型的类名和主键智能地标记任务:
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 或短信通知时,您应该查看相关通知通道的先决条件。
如果您希望在某个队列等待时间过长时收到通知,您可以使用 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,
],将队列的阈值设置为 0 将禁用该队列的长时间等待通知。
Horizon 包含一个指标仪表盘,提供有关您的任务和队列等待时间以及吞吐量的信息。为了填充此仪表盘,您应该配置 Horizon 的 snapshot Artisan 命令,使其在应用程序的 routes/console.php 文件中每五分钟运行一次:
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