基于 Laravel Horizon 提供队列系统解决方案


laravel horizon

简介

Horizon 为 Laravel 提供了基于 Redis 的、拥有美观后台的、代码驱动配置的队列系统。Horizon 让我们可以轻松监控队列系统的关键指标,例如任务吞吐量、运行时间和失败任务等。

所有的队列进程配置都存放在一个单独的简单配置文件中,这样的话配置文件就可以存放到源码控制以便团队所有成员的协作。

Horizon 管理后台

安装

注:需要确保在 queue 配置文件中将队列驱动被设置为 redis

我们使用 Composer 安装 Horizon 到 Laravel 项目:

composer require laravel/horizon

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

php artisan horizon:install

配置

发布好前端资源后,主配置文件就会出现在 config/horizon.php。在这个配置文件中,你可以配置队列进程选项以及每个包含目的描述的配置项,所以使用 Horizon 前务必浏览下这个配置文件。

注:需要确保 horizon 配置文件的 environments 部分包含了每个你计划运行 Horizon 的环境的入口。

负载均衡配置

Horizon 提供了三种负载均衡策略以供选择:simpleautofalsesimple 是默认策略,在进程之间平均分配进入任务:

'balance' => 'simple',

auto 策略基于队列当前负载调整每个队列的工作进程数量。例如,如果 notifications 队列有 1000 个等待执行的任务而 render 队列是空的,那么 Horizon 将会为 notifications 队列分配更多的工作进程直到队列为空。如果把 balance 选项设置为 false,就会使用默认的 Laravel 行为,也就是按照配置文件中的排列顺序处理队列。

使用 auto 策略时,可以定义 minProcessesmaxProcesses 配置项来控制 Horizon 进程的最小值和最大值:

'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default'],
            'balance' => 'auto',
            'minProcesses' => 1,
            'maxProcesses' => 10,
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
            'tries' => 3,
        ],
    ],
],    

balanceMaxShiftbalanceCooldown 配置值用于决定 Horizon 以多块的速度进行伸缩以便匹配队列进程的需求。在上面这个示例配置中,每 3 秒最多只会创建或删除 1 个新的队列处理进程。你可以按照应用需要对这些配置值进行自定义。

任务裁剪

horizon 配置文件允许你配置最近和执行失败任务保留的时长(单位:分钟),默认情况下,最近任务保留1个小时,执行失败任务保留1周:

'trim' => [
    'recent' => 60,
    'failed' => 10080,
],

后台授权

我们可以通过 /horizon 访问 Horizon 后台:

默认情况下,你只能在 local 环境下访问这个后台。在 app/Providers/HorizonServiceProvider.php 文件中,有一个 gate 方法,该授权 gate 用于控制非本地环境对 Horizon 的访问。你可以按照需要编辑这个 gate 方法来限制用户对 Horizon 的访问:

/**
 * Register the Horizon gate.
 *
 * This gate determines who can access Horizon in non-local environments.
 *
 * @return void
 */
protected function gate()
{
    Gate::define('viewHorizon', function ($user) {
        return in_array($user->email, [
            'test@xueyuanjun.com',
        ]);
    });
}

注:记住 Laravel 会自动注入认证用户到 Gate,如果你的应用通过其它方法来保障 Horizon 安全,例如 IP 限制,则你的 Horizon 用户不需要「登录」。因此,你需要修改上述 function ($user)function ($user = null) 来强制 Laravel 不要求认证。

升级 Horizon

升级到最新版本的 Horizon 时,需要仔细阅读升级指南

此外,你还要重新发布 Horizon 资源文件:

php artisan horizon:publish

要保持资源文件处于最新状态,从而避免以后更新可能出现的问题,可以添加如下命令到 composer.json 文件的 post-update-cmd 脚本:

{
    "scripts": {
        "post-update-cmd": [
            "@php artisan horizon:publish --ansi"
        ]
    }
}

运行 Horizon

如果你已经在配置文件 config/horizon.php 中配置过工作进程,就可以使用 Artisan 命令 horizon 来启动 Horizon,该命令会启动所有配置的工作进程:

php artisan horizon

你可以使用 Artisan 命令 horizon:pausehorizon:continue 来暂停或继续处理队列任务:

php artisan horizon:pause

php artisan horizon:continue

要查看 Horizon 的当前状态,可以使用 Artisan 命令 horizon:status

php artisan horizon:status

你还可以使用 Artisan 命令 horizon:terminate 来优雅地终止 Horizon 主进程 —— Horizon 会在所有当前正在执行的任务全部完成后退出:

php artisan horizon:terminate

部署 Horizon

如果要将 Horizon 部署到线上服务器,需要配置一个进程监控来监控 php artisan horizon 命令的运行并在异常退出的情况下重启该进程。部署新代码到服务器的时候,需要终止 Horizon 主进程,然后再通过进程监控程序重启以便让最新代码生效。

安装 Supervisor

Supervisor 是一个适用于 Linux 操作系统的优秀进程监控程序,它可以实现 horizon 进程失败后自动重启,要安装 Supervisor,以 Ubuntu 为例,可以使用如下命令:

sudo apt-get install supervisor

注:如果自己配置 Supervisor 对你而言很困难,可以考虑使用 Laravel Forge,它会自动安装并为你配置好 Laravel 项目。

Supervisor 配置

Supervisor 配置文件通常存放在 /etc/supervisor/conf.d 目录下,在这个目录中,你可以创建任意数量的配置文件来告知 Supervisor 你的进程需要被如何监控,和 Nginx 虚拟主机配置文件类似,通常我们会为每个被监控的进程创建一个独立的配置文件。例如,我们创建一个 horizon.conf 文件来配置 horizon 进程的启动和监控(请按照自己的实际情况修改相应的文件路径):

[program:horizon]
process_name=%(program_name)s
command=php /path/to/project/artisan horizon
autostart=true
autorestart=true
user=root
redirect_stderr=true
stdout_logfile=/path/to/project/storage/logs/horizon.log
stopwaitsecs=3600

需要确保 stopwaitsecs 的值大于最长耗时任务的运行时间,否则,Supervisor 会在该任务执行完毕前杀死该任务。

启动 Supervisor

配置文件创建好了之后,你可以使用如下命令更新 Supervisor 配置并启动进程:

sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start horizon

更多关于 Supervisor 的信息,请参考 Supervisor 官方文档

标签

Horizon 允许分配「标签」到任务,包括邮件、事件广播、通知以及队列事件监听器等。实际上,Horizon 会基于附加到任务的 Eloquent 模型为大部分任务以智能的方式自动打上标签。例如,我们来看看下面这个例子:

<?php
    
namespace App\Jobs;
    
use App\Models\Video;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
    
class RenderVideo implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    /**
     * The video instance.
     *
     * @var \App\Video
     */
    public $video;
    
    /**
     * Create a new job instance.
     *
     * @param  \App\Video  $video
     * @return void
     */
    public function __construct(Video $video)
    {
        $this->video = $video;
    }
    
    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        //
    }
}

如果任务被推送到队列时附带了一个 id1App\Models\Video 实例,它将会自动打上 App\Models\Video:1 的标签,这是因为 Horizon 会检查队列任务属性中的 Eloquent 模型,如果 Eloquent 模型被找到,Horizon 就会使用模型类名和主键为任务智能地打上标签:

$video = App\Models\Video::find(1);

App\Jobs\RenderVideo::dispatch($video);

手动打标签

如果你想要手动定义某个队列对象的标签,可以在该类中定义一个 tags 方法:

class RenderVideo implements ShouldQueue
{
    /**
     * Get the tags that should be assigned to the job.
     *
     * @return array
     */
    public function tags()
    {
        return ['render', 'video:'.$this->video->id];
    }
}

通知

注:配置 Horizon 发送 Slack 或者短信通知前,请先回顾下相关的通知驱动准备工作

如果你想要在某个队列任务等待很长时间后被通知,可以使用 Horizon::routeMailNotificationsToHorizon::routeSlackNotificationsTo 以及 Horizon::routeSmsNotificationsTo 方法。你可以在 HorizonServiceProvider 中调用这些方法:

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

配置通知等待时间下限

你可以在配置文件 config/horizon.php 中通过修改 waits 配置项来配置每个连接/队列上的等待时间下限(秒):

'waits' => [
    'redis:default' => 60,
    'redis:critical,high' => 90,
],

监控

Horizon 提供了一个监控后台查看任务和队列的等待时间和吞吐量信息,为了获取实时信息,可以配置 Horizon 的 Artisan 命令 snapshot 通过应用的调度器每五分钟运行一次:

/**
 * Define the application's command schedule.
 *
 * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 * @return void
 */
protected function schedule(Schedule $schedule)
{
    $schedule->command('horizon:snapshot')->everyFiveMinutes();
}

点赞 取消点赞 收藏 取消收藏

<< 上一篇: 基于 Laravel Fortify 提供纯后端用户认证解决方案

>> 下一篇: 基于 Laravel Homestead 搭建虚拟化本地集成开发环境