Skip to content

Laravel Horizon

简介

NOTE

在深入了解 Laravel Horizon 之前,您应该熟悉 Laravel 的基本队列服务。Horizon 增强了 Laravel 的队列,增加了额外的功能,如果您还不熟悉 Laravel 提供的基本队列功能,可能会感到困惑。

Laravel Horizon 为您的 Laravel 驱动的 Redis 队列提供了漂亮的仪表板和代码驱动的配置。Horizon 允许您轻松监控队列系统的关键指标,如作业吞吐量、运行时间和作业失败。

使用 Horizon 时,所有队列工作进程配置都存储在一个简单、单一的配置文件中。通过在版本控制文件中定义应用程序的工作进程配置,您可以在部署应用程序时轻松扩展或修改应用程序的队列工作进程。

安装

WARNING

Laravel Horizon 要求您使用 Redis 来驱动您的队列。因此,您应该确保在应用程序的 config/queue.php 配置文件中将队列连接设置为 redis。Horizon 目前不兼容 Redis 集群。

您可以使用 Composer 包管理器将 Horizon 安装到您的项目中:

shell
composer require laravel/horizon

安装 Horizon 后,使用 horizon:install Artisan 命令发布其资源:

shell
php artisan horizon:install

配置

发布 Horizon 的资源后,其主要配置文件将位于 config/horizon.php。此配置文件允许您为应用程序配置队列工作进程选项。每个配置选项都包含其用途的描述,因此请务必彻底探索此文件。

WARNING

Horizon 在内部使用名为 horizon 的 Redis 连接。此 Redis 连接名称是保留的,不应在 database.php 配置文件中分配给另一个 Redis 连接,也不应作为 horizon.php 配置文件中 use 选项的值。

环境

安装后,您应该熟悉的主要 Horizon 配置选项是 environments 配置选项。此配置选项是应用程序运行的环境数组,并为每个环境定义工作进程选项。默认情况下,此条目包含 productionlocal 环境。但是,您可以根据需要自由添加更多环境:

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

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

您还可以定义通配符环境(*),当没有找到其他匹配环境时将使用它:

php
'environments' => [
    // ...

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

启动 Horizon 时,它将使用应用程序运行环境的工作进程配置选项。通常,环境由 APP_ENV 环境变量的值确定。例如,默认的 local Horizon 环境配置为启动三个工作进程,并自动平衡分配给每个队列的工作进程数量。默认的 production 环境配置为最多启动 10 个工作进程,并自动平衡分配给每个队列的工作进程数量。

WARNING

您应该确保 horizon 配置文件的 environments 部分包含您计划运行 Horizon 的每个环境的条目。

主管

如您在 Horizon 的默认配置文件中所见,每个环境可以包含一个或多个"主管"。默认情况下,配置文件将此主管定义为 supervisor-1;但是,您可以自由命名您的主管。每个主管本质上负责"监督"一组工作进程,并负责在队列之间平衡工作进程。

如果您想定义应该在该环境中运行的新工作进程组,可以向给定环境添加其他主管。如果应用程序使用的给定队列需要不同的平衡策略或工作进程数量,您可以选择这样做。

维护模式

当应用程序处于维护模式时,除非在 Horizon 配置文件中将主管的 force 选项定义为 true,否则 Horizon 不会处理排队的作业:

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

默认值

在 Horizon 的默认配置文件中,您会注意到 defaults 配置选项。此配置选项指定应用程序主管的默认值。主管的默认配置值将合并到每个环境的主管配置中,允许您在定义主管时避免不必要的重复。

仪表板授权

可以通过 /horizon 路由访问 Horizon 仪表板。默认情况下,您只能在 local 环境中访问此仪表板。但是,在您的 app/Providers/HorizonServiceProvider.php 文件中,有一个授权门定义。此授权门控制非本地环境中对 Horizon 的访问。您可以根据需要修改此门,以限制对 Horizon 安装的访问:

php
/**
 * 注册 Horizon 门。
 *
 * 此门确定谁可以在非本地环境中访问 Horizon。
 */
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 的默认队列服务和"尝试次数"的概念。

您可以在主管配置中定义作业可以消耗的最大尝试次数:

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

NOTE

此选项类似于使用 Artisan 命令处理队列时的 --tries 选项。

当使用 WithoutOverlappingRateLimited 等中间件时,调整 tries 选项至关重要,因为它们会消耗尝试次数。要处理此问题,可以在主管级别调整 tries 配置值,或者在作业类上定义 $tries 属性。

如果您不设置 tries 选项,Horizon 默认为单次尝试,除非作业类定义了 $tries,后者优先于 Horizon 配置。

tries$tries 设置为 0 允许无限次尝试,这在尝试次数不确定时很理想。为了防止无休止的失败,您可以通过在作业类上设置 $maxExceptions 属性来限制允许的异常数量。

作业超时

同样,您可以在主管级别设置 timeout 值,该值指定工作进程在强制终止之前可以运行作业多少秒。一旦终止,作业将被重试或标记为失败,具体取决于您的队列配置:

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

WARNING

当使用 auto 平衡策略时,Horizon 会将正在进行的工作进程视为"挂起",并在缩减期间在 Horizon 超时后强制终止它们。始终确保 Horizon 超时大于任何作业级别的超时,否则作业可能在执行中途被终止。此外,timeout 值应始终比 config/queue.php 配置文件中定义的 retry_after 值短至少几秒。否则,您的作业可能会被处理两次。

作业退避

您可以在主管级别定义 backoff 值,以指定 Horizon 在重试遇到未处理异常的作业之前应等待多长时间:

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

您还可以通过使用数组作为 backoff 值来配置"指数"退避。在此示例中,第一次重试的重试延迟将为 1 秒,第二次重试为 5 秒,第三次重试为 10 秒,如果还有更多尝试剩余,每次后续重试为 10 秒:

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

静默作业

有时,您可能不想查看应用程序或第三方包分发的某些作业。您可以将这些作业静默,而不是让它们占用"已完成作业"列表中的空间。首先,将作业的类名添加到应用程序的 horizon 配置文件中的 silenced 配置选项:

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

除了静默单个作业类外,Horizon 还支持基于标签静默作业。如果您想隐藏共享公共标签的多个作业,这很有用:

php
'silenced_tags' => [
    'notifications'
],

或者,您希望静默的作业可以实现 Laravel\Horizon\Contracts\Silenced 接口。如果作业实现了此接口,它将自动被静默,即使它不存在于 silenced 配置数组中:

php
use Laravel\Horizon\Contracts\Silenced;

class ProcessPodcast implements ShouldQueue, Silenced
{
    use Queueable;

    // ...
}

平衡策略

每个主管可以处理一个或多个队列,但与 Laravel 的默认队列系统不同,Horizon 允许您从三种工作进程平衡策略中进行选择:autosimplefalse

自动平衡

auto 策略是默认策略,根据队列的当前工作负载调整每个队列的工作进程数量。例如,如果您的 notifications 队列有 1,000 个待处理作业,而 default 队列为空,Horizon 将向 notifications 队列分配更多工作进程,直到队列为空。

使用 auto 策略时,您还可以配置 minProcessesmaxProcesses 配置选项:

  • minProcesses 定义每个队列的最小工作进程数。此值必须大于或等于 1。
  • maxProcesses 定义 Horizon 可以扩展到的所有队列的最大工作进程总数。此值通常应大于队列数乘以 minProcesses 值。要防止主管生成任何进程,可以将此值设置为 0。

例如,您可以配置 Horizon 为每个队列维护至少一个进程,并扩展到总共 10 个工作进程:

php
'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 策略将根据队列上的作业总数分配工作进程。

balanceMaxShiftbalanceCooldown 配置值确定 Horizon 扩展以满足工作进程需求的速度。在上面的示例中,每三秒最多创建或销毁一个新进程。您可以根据应用程序的需要自由调整这些值。

队列优先级和自动平衡

使用 auto 平衡策略时,Horizon 不会在队列之间强制执行严格的优先级。主管配置中队列的顺序不影响工作进程的分配方式。相反,Horizon 依赖所选的 autoScalingStrategy 根据队列负载动态分配工作进程。

例如,在以下配置中,high 队列不会优先于 default 队列,尽管它在列表中排在第一位:

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

如果您需要在队列之间强制执行相对优先级,可以定义多个主管并显式分配处理资源:

php
'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 不会自动扩展工作进程数量。相反,它使用固定数量的进程:

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

在上面的示例中,Horizon 将为每个队列分配 5 个进程,将总共 10 个进程平均分配。

如果您想单独控制分配给每个队列的工作进程数量,可以定义多个主管:

php
'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 的默认队列系统。但是,如果作业开始积累,它仍会扩展工作进程数量:

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

在上面的示例中,default 队列中的作业始终优先于 notifications 队列中的作业。例如,如果 default 中有 1,000 个作业,而 notifications 中只有 10 个,Horizon 将在处理 notifications 中的任何作业之前完全处理所有 default 作业。

您可以使用 minProcessesmaxProcesses 选项控制 Horizon 扩展工作进程的能力:

  • minProcesses 定义总的最小工作进程数。此值必须大于或等于 1。
  • maxProcesses 定义 Horizon 可以扩展到的最大工作进程总数。

升级 Horizon

升级到 Horizon 的新主要版本时,务必仔细查看升级指南

运行 Horizon

在应用程序的 config/horizon.php 配置文件中配置主管和工作进程后,可以使用 horizon Artisan 命令启动 Horizon。此单个命令将启动当前环境的所有配置工作进程:

shell
php artisan horizon

您可以使用 horizon:pausehorizon:continue Artisan 命令暂停 Horizon 进程并指示它继续处理作业:

shell
php artisan horizon:pause

php artisan horizon:continue

您还可以使用 horizon:pause-supervisorhorizon:continue-supervisor Artisan 命令暂停和继续特定的 Horizon 主管

shell
php artisan horizon:pause-supervisor supervisor-1

php artisan horizon:continue-supervisor supervisor-1

您可以使用 horizon:status Artisan 命令检查 Horizon 进程的当前状态:

shell
php artisan horizon:status

您可以使用 horizon:supervisor-status Artisan 命令检查特定 Horizon 主管的当前状态:

shell
php artisan horizon:supervisor-status supervisor-1

部署 Horizon

将 Horizon 部署到应用程序的实际服务器时,应该配置进程监视器来监控 php artisan horizon 命令,并在它意外退出时重新启动它。在将新代码部署到服务器时,需要指示主管终止当前的 Horizon 进程,以便进程监视器重新启动它并接收您的代码更改。

或者,可以使用 horizon:terminate Artisan 命令优雅地终止 Horizon。Horizon 将在终止之前完成所有当前正在处理的作业:

shell
php artisan horizon:terminate

安装 Supervisor

Supervisor 是 Linux 操作系统的进程监视器,如果 horizon 进程退出,它将自动重新启动。要在 Ubuntu 上安装 Supervisor,可以使用以下命令:

shell
sudo apt-get install supervisor

NOTE

如果自己配置 Supervisor 让您感到不知所措,请考虑使用 Laravel Forge,它将自动为您的 Laravel 项目安装和配置 Supervisor。

Supervisor 配置

Supervisor 配置文件通常存储在 /etc/supervisor/conf.d 目录中。在此目录中,您可以创建任意数量的配置文件,指示主管如何监控您的进程。例如,让我们创建一个启动并监控 horizon 进程的 horizon.conf 文件:

ini
[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 可能会在作业完成处理之前终止它。

启动 Supervisor

创建配置文件后,可以使用以下命令更新 Supervisor 配置并启动受监控的进程:

shell
sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start horizon

NOTE

有关运行 Supervisor 的更多信息,请查阅 Supervisor 文档

标签

Horizon 允许您为作业分配"标签",包括邮箱、作业 ID 等。您可以在 Horizon 仪表板中按这些标签搜索作业。如果您想为 Horizon 分发的作业分配标签,可以在作业类上定义 tags 方法:

php
use App\Models\User;
use App\Models\Video;

class RenderVideo implements ShouldQueue
{
    // ...

    /**
     * 获取应分配给作业的标签。
     *
     * @return array<int, string>
     */
    public function tags(): array
    {
        return ['render', 'video:'.$this->video->id];
    }
}

通知

WARNING

在使用通知之前,您应该为您的项目注册 Notification 服务。如果您计划使用 Slack 通知,您还应该为您的项目注册 Slack 通知通道

如果配置了通知,当队列达到特定等待时间或吞吐量阈值时,Horizon 将发送通知。例如,如果队列在 90 分钟内积压了超过 1,000 个作业,您可能希望收到通知。您可以在应用程序的 config/horizon.php 配置文件中使用 notifications 配置选项配置通知:

php
'notifications' => [
    'slack' => [
        'endpoint' => 'https://hooks.slack.com/services/...',
    ],

    'sms' => [
        'to' => '15556667777',
    ],

    'mail' => [
        'to' => 'root@example.com',
    ],
],

除了 Slack 通知端点外,您还可以提供 Discord、Microsoft Teams 和 Telegram 端点:

php
'notifications' => [
    'discord' => [
        'endpoint' => 'https://discord.com/api/webhooks/...',
    ],

    'microsoft-teams' => [
        'endpoint' => 'https://outlook.office.com/webhook/...',
    ],

    'telegram' => [
        'endpoint' => 'https://api.telegram.org/bot.../sendMessage',
    ],
],

通知阈值

您可以在 config/horizon.php 配置文件中配置每个应用程序/环境组合需要多少时间等待和总作业数阈值:

php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
            'wait' => [
                'redis:default' => 90,
            ],
        ],
    ],
],

在此示例中,如果 redis 连接上的 default 队列等待时间超过 90 秒,将发送通知。您可以选择将 wait 配置选项设置为 false 以禁用特定环境的等待时间通知:

php
'wait' => [
    'redis:default' => false,
],

您还可以通过在环境配置中添加 throughput 选项来配置吞吐量阈值。如果队列在一分钟内处理的作业少于指定数量,将发送通知:

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

通知节流

默认情况下,通知将在达到配置的阈值后立即发送。您可以使用 throttle 选项控制通知的发送频率。例如,要将通知限制为每小时最多发送一次:

php
'notifications' => [
    'slack' => [
        'endpoint' => 'https://hooks.slack.com/services/...',
        'throttle' => 60, // 每小时最多发送一次通知
    ],
],

指标

Horizon 包含一个指标仪表板,提供有关队列作业和等待时间的信息。要通过此仪表板填充数据,您应该配置 Horizon 的 snapshot Artisan 命令每五分钟运行一次:

shell
php artisan horizon:snapshot

在应用程序的 routes/console.php 文件中调用 snapshot 方法将自动为您处理:

php
use Illuminate\Support\Facades\Schedule;

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

删除失败作业

如果您想删除失败作业,可以使用 horizon:forget 命令。horizon:forget 命令接受失败作业的 ID 或 UUID:

shell
php artisan horizon:forget 5

清除队列中的作业

如果您想从特定队列中删除所有作业,可以使用 horizon:clear 命令。您可以通过 queue 选项指定要清除的队列:

shell
php artisan horizon:clear --queue=default

默认情况下,horizon:clear 不会删除任何正在处理的作业。但是,您可以使用 force 选项强制删除这些作业:

shell
php artisan horizon:clear --queue=default --force