Skip to content

Laravel Horizon

简介

NOTE

在深入了解 Laravel Horizon 之前,你应该先熟悉 Laravel 的基本队列服务。Horizon 为 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 配置选项。此配置选项是应用程序运行的环境数组,并定义了每个环境的工作进程选项。默认情况下,此条目包含 productionlocal 环境。但是,你可以根据需要添加更多环境:

    '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 配置选项。此配置选项指定应用程序的 监督者 的默认值。监督者的默认配置值将与每个环境的监督者配置合并,从而避免在定义监督者时出现不必要的重复。

负载均衡策略

与 Laravel 的默认队列系统不同,Horizon 允许你从三种工作进程负载均衡策略中选择: simple, autofalsesimple 策略将传入的任务均匀分配给工作进程:

'balance' => 'simple',

auto 策略,这是配置文件的默认策略,根据队列的当前工作负载调整每个队列的工作进程数量。例如,如果你的 notifications 队列有 1,000 个待处理的任务,而你的 render 队列是空的,Horizon 将为你的 notifications 队列分配更多的工作进程,直到该队列为空。

使用 auto 策略时,你可以定义 minProcessesmaxProcesses 配置选项,以控制 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 是根据清空队列所需的总时间 (time 策略) 还是根据队列上的总任务数 (size 策略) 来分配更多的工作进程给队列。

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

balance 选项设置为 false 时,将使用默认的 Laravel 行为,即队列按照配置中列出的顺序处理。

仪表板授权

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 不要求认证。

静默任务

有时,你可能不想查看应用程序或第三方包分派的某些任务。这些任务不应占用"已完成任务"列表的空间,你可以将它们静默化。要开始,将任务的类名添加到应用程序的 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 配置文件中的监督者和工作进程后,你可以使用 horizon Artisan 命令启动 Horizon。此单一命令将为当前环境启动所有配置的工作进程:

php artisan horizon

你可以使用 horizon:pausehorizon:continue Artisan 命令暂停 Horizon 进程,并指示它继续处理任务:

php artisan horizon:pause

php artisan horizon:continue

你还可以使用 horizon:pause-supervisorhorizon: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: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 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 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 属性为 1App\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];
        }
    }

手动标记事件监听器

When retrieving the tags for a queued event listener, Horizon will automatically pass the event instance to the tags method, allowing you to add event data to the tags:

php
    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];
        }
    }

Notifications

WARNING

When configuring Horizon to send Slack or SMS notifications, you should review the prerequisites for the relevant notification channel.

If you would like to be notified when one of your queues has a long wait time, you may use the Horizon::routeMailNotificationsTo, Horizon::routeSlackNotificationsTo, and Horizon::routeSmsNotificationsTo methods. You may call these methods from the boot method of your application's App\Providers\HorizonServiceProvider:

php
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        parent::boot();

        Horizon::routeSmsNotificationsTo('15556667777');
        Horizon::routeMailNotificationsTo('example@example.com');
        Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
    }

Configuring Notification Wait Time Thresholds

You may configure how many seconds are considered a "long wait" within your application's config/horizon.php configuration file. The waits configuration option within this file allows you to control the long wait threshold for each connection / queue combination. Any undefined connection / queue combinations will default to a long wait threshold of 60 seconds:

php
    'waits' => [
        'redis:critical' => 30,
        'redis:default' => 60,
        'redis:batch' => 120,
    ],

Metrics

Horizon 包括一个指标仪表板,该仪表板提供有关您的作业和队列等待时间以及吞吐量的信息。为了填充此仪表板,您应该在应用程序的 routes/console.php 文件中将 Horizon 的快照 Artisan 命令配置为每 5 分钟运行一次:

php
    use Illuminate\Support\Facades\Schedule;

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

删除失败的作业

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

shell
php artisan horizon:forget 5

如果您想删除所有失败的作业,您可以为 horizon:forget 命令提供 --all 选项:

shell
php artisan horizon:forget --all

从队列中清除作业

如果您想从应用程序的默认队列中删除所有作业,可以使用 horizon:clear Artisan 命令来实现:

shell
php artisan horizon:clear

您可以提供 queue 选项以从特定队列中删除作业:

shell
php artisan horizon:clear --queue=emails