Horizon
Laravel Horizon
简介
[!注意]
在深入了解 Laravel Horizon 之前,你应该先熟悉 Laravel 的基础 队列服务。
Horizon 在 Laravel 队列的基础上提供了额外功能,如果你对 Laravel 提供的基础队列功能还不熟悉,这些可能会让你感到困惑。
Laravel Horizon 为你的 Laravel 驱动的 Redis 队列 提供了一个优雅的仪表盘和代码驱动的配置。
Horizon 允许你轻松监控队列系统的关键指标,例如任务吞吐量、运行时长以及任务失败情况。
使用 Horizon 时,你的所有队列 worker 配置都会存储在一个简单的配置文件中。
通过在版本控制文件中定义应用的 worker 配置,你可以在部署应用时轻松扩展或修改队列 worker。
安装
[!警告]
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 的选项。
每个配置选项都包含了它的用途说明,因此请务必仔细阅读这个文件。
[!警告]
Horizon 在内部使用一个名为horizon
的 Redis 连接。
这个 Redis 连接名称是保留的,不应在database.php
配置文件中分配给其他 Redis 连接,也不应作为horizon.php
配置文件中use
选项的值。
环境
安装完成后,你需要熟悉的主要 Horizon 配置项是 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
Horizon 环境被配置为启动 3 个 worker 进程,并自动平衡分配到每个队列的 worker 进程数量。
而默认的 production
环境则被配置为最多启动 10 个 worker 进程,并自动平衡分配到每个队列的 worker 进程数量。
[!警告]
你需要确保horizon
配置文件中的environments
部分包含你计划运行 Horizon 的每个 环境 的条目。
监督器(Supervisors)
正如你在 Horizon 的默认配置文件中所看到的,每个环境可以包含一个或多个“监督器”。
默认情况下,配置文件将这个监督器定义为 supervisor-1
;不过,你可以自由地为监督器命名。
每个监督器本质上负责“监督”一组 worker 进程,并负责在队列之间平衡 worker 进程。
如果你希望在某个环境中定义一组新的 worker 进程,可以为该环境添加额外的监督器。
当你希望为应用使用的某个队列定义不同的负载均衡策略或不同的 worker 进程数量时,这样做是有意义的。
维护模式(Maintenance Mode)
当你的应用处于 维护模式 时,Horizon 不会处理队列任务,
除非你在 Horizon 配置文件中将监督器的 force
选项定义为 true
:
'environments' => [
'production' => [
'supervisor-1' => [
// ...
'force' => true,
],
],
],
默认值(Default Values)
在 Horizon 的默认配置文件中,你会注意到一个 defaults
配置项。
该配置项指定了应用 监督器 的默认值。
监督器的默认配置值会被合并到各环境的监督器配置中,从而避免你在定义监督器时出现不必要的重复。
负载均衡策略(Balancing Strategies)
与 Laravel 的默认队列系统不同,Horizon 允许你从三种 worker 负载均衡策略中进行选择:simple
、auto
和 false
。
其中,simple
策略会将传入的任务均匀分配给 worker 进程:
'balance' => 'simple',
auto
策略是配置文件中的默认值,它会根据队列当前的工作负载来调整每个队列的 worker 进程数量。
例如,如果你的 notifications
队列有 1,000 个待处理任务,而 render
队列为空,Horizon 会分配更多的 worker 到 notifications
队列,直到该队列被清空。
当使用 auto
策略时,你可以定义 minProcesses
和 maxProcesses
配置项,用于控制每个队列的最小进程数,以及 Horizon 在扩缩容时所允许的最大进程数:
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'minProcesses' => 1,
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
'tries' => 3,
],
],
],
autoScalingStrategy
配置项决定 Horizon 在为队列分配更多 worker 进程时,依据的标准是队列清空所需的总时间(time
策略),还是依据队列中的任务总数(size
策略)。
balanceMaxShift
和 balanceCooldown
配置项决定 Horizon 扩缩容的速度。
在上面的示例中,最多每三秒会创建或销毁一个新的进程。
你可以根据应用的需求自由调整这些值。
当 balance
选项被设置为 false
时,将使用 Laravel 的默认行为,队列会按照你在配置中列出的顺序依次处理。
仪表盘授权(Dashboard Authorization)
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',
]);
});
}
替代认证策略(Alternative Authentication Strategies)
请记住,Laravel 会自动将已认证的用户注入到 gate 闭包中。
如果你的应用通过其他方式提供 Horizon 的安全性,例如 IP 限制,那么你的 Horizon 用户可能不需要“登录”。
因此,你需要将上面闭包的签名 function (User $user)
改为 function (User $user = null)
,以强制 Laravel 不要求认证。
静默任务(Silenced Jobs)
有时,你可能不希望查看应用或第三方包派发的某些任务。
与其让这些任务占用“已完成任务”列表中的空间,你可以将它们静默。
要开始操作,请在应用的 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(Upgrading Horizon)
当升级到 Horizon 的新主版本时,务必仔细查看 升级指南。
运行 Horizon(Running Horizon)
一旦你在应用的 config/horizon.php
配置文件中配置了监督器和 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 监督器:
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(Deploying Horizon)
当你准备将 Horizon 部署到应用的实际服务器时,应配置一个进程监控器来监控 php artisan horizon
命令,并在它意外退出时重新启动它。
不用担心,我们将在下面讨论如何安装进程监控器。
在应用的部署过程中,你应指示 Horizon 进程终止,以便你的进程监控器重新启动它,并应用你的代码更改:
php artisan horizon:terminate
安装 Supervisor
Supervisor 是 Linux 操作系统的进程监控器,如果你的 horizon
进程停止执行,它会自动重新启动。
在 Ubuntu 上安装 Supervisor,可以使用以下命令。如果你不是使用 Ubuntu,也可以通过操作系统的包管理器安装 Supervisor:
sudo apt-get install supervisor
[!注意]
如果自己配置 Supervisor 感到繁琐,可以考虑使用 Laravel Cloud,它可以管理 Laravel 应用的后台进程。
监督器配置(Supervisor Configuration)
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 可能会在任务完成前就将其终止。
[!警告]
上述示例适用于基于 Ubuntu 的服务器,但其他操作系统的 Supervisor 配置文件的位置和文件扩展名可能有所不同。
请查阅你的服务器文档以获取更多信息。
启动 Supervisor(Starting Supervisor)
创建配置文件后,你可以使用以下命令更新 Supervisor 配置并启动被监控的进程:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start horizon
[!注意]
有关运行 Supervisor 的更多信息,请查阅 Supervisor 文档。
标签
Horizon 允许你为任务分配“标签”,包括可邮件任务(mailables)、广播事件(broadcast events)、通知(notifications)和队列事件监听器(queued event listeners)。
事实上,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);
手动为任务打标签(Manually Tagging Jobs)
如果你希望手动定义某个可队列对象的标签,可以在类中定义一个 tags
方法:
class RenderVideo implements ShouldQueue
{
/**
* 获取应分配给任务的标签
*
* @return array<int, string>
*/
public function tags(): array
{
return ['render', 'video:'.$this->video->id];
}
}
手动为事件监听器打标签(Manually Tagging Event Listeners)
在获取队列事件监听器的标签时,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
。
你可以在应用的 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,
],
指标
Horizon 包含一个指标仪表盘,用于提供有关任务和队列等待时间及吞吐量的信息。
为了填充该仪表盘,你应在应用的 routes/console.php
文件中配置 Horizon 的 snapshot
Artisan 命令每五分钟运行一次:
use Illuminate\Support\Facades\Schedule;
Schedule::command('horizon:snapshot')->everyFiveMinutes();
删除失败任务
如果你希望删除一个失败任务,可以使用 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
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: