Laravel Horizon

简介

Horizon 为 Laravel Redis 队列提供了漂亮的管理后台并可以用代码进行配置。Horizon 允许您轻松监控队列系统的关键指标,例如任务吞吐量,运行时间和失败的任务。

所有队列处理器配置都存储在单个简单的配置文件中,允许整个团队通过版本控制协作维护配置。

安装

由于 Horizon 使用了异步处理信号,因此需要 PHP 7.1+。其次,应该确保在 queue 配置文件中将队列驱动设置为 redis

可以在 Laravel 项目中使用 Composer 安装 Horizon:

composer require laravel/horizon

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

php artisan vendor:publish --provider="Laravel\Horizon\HorizonServiceProvider"

配置

发布 Horizon 的资源后,其主要配置文件会位于 config/horizon.php。此配置文件允许您配置队列处理器选项,并且每个配置选项都包含其作用描述,确保完整查看此文件。

负载均衡选项

Horizon 允许您从三种负载均衡策略中选择:simpleautofalsesimple 是默认的策略,它会在进程间均匀分配传入的任务:

'balance' => 'simple',

auto 策略会基于当前队列负载调整每个队列的队列处理器进程的数量。例如,如果 notifications 队列有 1000 个等待处理的任务,而 render 队列是空的,Horizon 会分配更多队列处理器给 notifications 队列,直到它为空。当 balance 选项设置为 false 时,会使用 Laravel 默认的行为,即按配置文件中列出的顺序处理队列。

管理后台身份认证

Horizon 的管理后台是 /horizon。默认情况下,只能在 local 环境访问此管理后台。要为管理后台定义一个更具体的访问策略,可以使用 Horizon::auth 方法。auth 方法接收一个回调,此回调返回 truefalse,表明用户是否有权访问 Horizon 管理后台。通常情况下,应该在 AppServiceProviderboot 方法中调用 Horizon::auth

Horizon::auth(function ($request) {
    // return true / false;
});

运行 Horizon

config/horizon.php 配置文件中配置队列处理器后,可以使用 Artisan 命令 horizon 启动 Horizon。此命令会启动所有配置的队列处理器:

php artisan horizon

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

php artisan horizon:pause

php artisan horizon:continue

可以使用 Artisan 命令 horizon:terminate 在计算机上优雅地结束 Horizon 主进程。Horizon 会在完成当前正在处理的任务后退出:

php artisan horizon:terminate

部署 Horizon

如果要将 Horizon 部署到生产服务器,应该配置一个进程管理器来监控 php artisan horizon 命令,并在其意外退出时重启它。当在服务器上部署新的代码时,需要指示 Horizon 主进程结束运行,以便进程管理器可以重启它,使代码更改生效。

Supervisor 配置

如果使用 Supervisor 进程管理器来管理 horizon 进程,以下配置文件应该可以满足需要:

[program:horizon]
process_name=%(program_name)s
command=php /home/forge/app.com/artisan horizon
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/app.com/horizon.log

如果不习惯管理自己的服务器,可以考虑使用 Laravel Forge。Forge 提供 PHP 7+ 的服务器,Horizon,以及运行现代化、强大的 Laravel 应用所需的一切。

标签

Horizon 允许您为任务分配「标签」,包括邮件,事件广播,通知和队列事件监听器。实际上,Laravel 会根据关联到任务的模型自动为大多数任务打上标签。例如,看一下如下任务:

namespace App\Jobs;

use App\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;

    /**
     * 视频实例
     *
     * @var \App\Video
     */
    public $video;

    /**
     * 创建新的任务实例
     *
     * @param  \App\Video  $video
     * @return void
     */
    public function __construct(Video $video)
    {
        $this->video = $video;
    }

    /**
     * 执行任务
     *
     * @return void
     */
    public function handle()
    {
        //
    }
}

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

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

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

手动添加标签

如果想要为可加入队列的对象手动定义标签,可以在类上定义一个 tags 方法:

class RenderVideo implements ShouldQueue
{
    /**
     * 获取应该分配给任务的标签
     *
     * @return array
     */
    public function tags()
    {
        return ['render', 'video:'.$this->video->id];
    }
}

通知

注意: 在使用通知前,应该将 guzzlehttp/guzzle Composer 扩展包添加到项目中。配置 Horizon 发送短信通知时,还应该查看 Nexmo 通知驱动前提

如果想要在其中一个队列有长等待时间的情况下通知开发者或运维人员,可以使用 Horizon::routeMailNotificationsToHorizon::routeSlackNotificationsToHorizon::routeSmsNotificationsTo 方法。然后在应用的 AppServiceProvider 中调用这些方法:

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

配置通知等待时间阈值

可以在 config/horizon.php 配置文件中配置被视为「长等待」的秒数。此文件中的 waits 配置项允许您控制每个连接/队列组合的长等待阈值:

'waits' => [
    'redis:default' => 60,
],

指标

Horizon 包含的指标管理后台提供了任务和队列的等待时间和吞吐量信息。为了填充对应信息,应该通过应用的 调度程序 将 Horizon 的 Artisan 命令配置为每五分钟运行一次:

/**
 * 定义应用的命令调度
 *
 * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 * @return void
 */
protected function schedule(Schedule $schedule)
{
    $schedule->command('horizon:snapshot')->everyFiveMinutes();
}