Laravel horizon monitoring queue

  • May 22, 2021
  • 2134

Laravel horizon package tuyệt vời giúp giám sát queue job khi sử dụng laravel. Horizon giúp bạn giám sát queue job cự kì dễ dàng với giao diện trực quan: quản lí job thực hiện thành công, failed job, retry job, số worker đang chạy... Khi sử dụng horizon tất cả các cấu hình liên quan đên queue job đều được quản lí trong file config/horizon.php giúp bạn dễ dàng thay đổi các thiết lập của queue worker.

Cài đặt laravel horizon

Để sử dụng laravel horizon dự án bắt buộc phải sử dùng redis queue. Vì vậy cần cài đặt PhpRedis extension và cấu hình queue connection là redis trong file .env


QUEUE_CONNECTION=redis

Để có cái nhìn tổng quan về queue trong laravel hãy đọc bài viết hướng dẫn laravel queue nhé :D Cài đặt package horizon cực kì đơn giản với composer


composer require laravel/horizon

Để sử dụng horizon chỉ cần chạy artisan command là xong


php artisan horizon:install

Tìm hiểu file config horizon

Khi cài đặt xong laravel horizon mọi cấu hình sẽ được quản lí trong file config/horizon.php


<?php

use Illuminate\Support\Str;

return [

    /*
    |--------------------------------------------------------------------------
    | Horizon Domain
    |--------------------------------------------------------------------------
    |
    | This is the subdomain where Horizon will be accessible from. If this
    | setting is null, Horizon will reside under the same domain as the
    | application. Otherwise, this value will serve as the subdomain.
    |
    */
    // Thay đổi domain truy cập vào trang quản trị
    'domain' => null,

    'basic_auth' => [
        'username' => env('HORIZON_BASIC_AUTH_USERNAME'),
        'password' => env('HORIZON_BASIC_AUTH_PASSWORD'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Horizon Path
    |--------------------------------------------------------------------------
    |
    | This is the URI path where Horizon will be accessible from. Feel free
    | to change this path to anything you like. Note that the URI will not
    | affect the paths of its internal API that aren't exposed to users.
    |
    */

    'path' => 'horizon',

    /*
    |--------------------------------------------------------------------------
    | Horizon Redis Connection
    |--------------------------------------------------------------------------
    |
    | This is the name of the Redis connection where Horizon will store the
    | meta information required for it to function. It includes the list
    | of supervisors, failed jobs, job metrics, and other information.
    |
    */

    'use' => 'default',

    /*
    |--------------------------------------------------------------------------
    | Horizon Redis Prefix
    |--------------------------------------------------------------------------
    |
    | This prefix will be used when storing all Horizon data in Redis. You
    | may modify the prefix when you are running multiple installations
    | of Horizon on the same server so that they don't have problems.
    |
    */

    'prefix' => env(
        'HORIZON_PREFIX',
        Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
    ),

    /*
    |--------------------------------------------------------------------------
    | Horizon Route Middleware
    |--------------------------------------------------------------------------
    |
    | These middleware will get attached onto each Horizon route, giving you
    | the chance to add your own middleware to this list or change any of
    | the existing middleware. Or, you can simply stick with this list.
    |
    */

    'middleware' => ['web', 'horizonBasicAuth'],

    /*
    |--------------------------------------------------------------------------
    | Queue Wait Time Thresholds
    |--------------------------------------------------------------------------
    |
    | This option allows you to configure when the LongWaitDetected event
    | will be fired. Every connection / queue combination may have its
    | own, unique threshold (in seconds) before this event is fired.
    |
    */

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

    /*
    |--------------------------------------------------------------------------
    | Job Trimming Times
    |--------------------------------------------------------------------------
    |
    | Here you can configure for how long (in minutes) you desire Horizon to
    | persist the recent and failed jobs. Typically, recent jobs are kept
    | for one hour while all failed jobs are stored for an entire week.
    |
    */
    
    // Các job thành công được hiển thị trong vòng 1h, các job thất bại là 1 tuần
    'trim' => [
        'recent' => 60,
        'pending' => 60,
        'completed' => 60, // 1hour
        'recent_failed' => 10080,
        'failed' => 10080, // 1 week
        'monitored' => 10080,
    ],

    /*
    |--------------------------------------------------------------------------
    | Metrics
    |--------------------------------------------------------------------------
    |
    | Here you can configure how many snapshots should be kept to display in
    | the metrics graph. This will get used in combination with Horizon's
    | `horizon:snapshot` schedule to define how long to retain metrics.
    |
    */

    'metrics' => [
        'trim_snapshots' => [
            'job' => 24,
            'queue' => 24,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Fast Termination
    |--------------------------------------------------------------------------
    |
    | When this option is enabled, Horizon's "terminate" command will not
    | wait on all of the workers to terminate unless the --wait option
    | is provided. Fast termination can shorten deployment delay by
    | allowing a new instance of Horizon to start while the last
    | instance will continue to terminate each of its workers.
    |
    */

    'fast_termination' => false,

    /*
    |--------------------------------------------------------------------------
    | Memory Limit (MB)
    |--------------------------------------------------------------------------
    |
    | This value describes the maximum amount of memory the Horizon master
    | supervisor may consume before it is terminated and restarted. For
    | configuring these limits on your workers, see the next section.
    |
    */

    'memory_limit' => 64,

    /*
    |--------------------------------------------------------------------------
    | Queue Worker Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may define the queue worker settings used by your application
    | in all environments. These supervisors and settings handle all your
    | queued jobs and will be provisioned by Horizon during deployment.
    |
    */

    'defaults' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default'],
            'balance' => 'auto',
            'maxProcesses' => 4,
            'memory' => 128,
            'tries' => 2,
            'nice' => 0,
        ],
    ],
   
    // Cấu hình theo môi trường
    'environments' => [
        'production' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'auto',
                'maxProcesses' => 4,
                'tries' => 2,
                'balanceMaxShift' => 1,
                'balanceCooldown' => 3,
            ],
        ],

        'staging' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'auto',
                'maxProcesses' => 4,
                'tries' => 2,
                'balanceMaxShift' => 1,
                'balanceCooldown' => 3,
            ],
        ],

        'development' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default'],
                'balance' => 'auto',
                'maxProcesses' => 4,
                'tries' => 2,
                'balanceMaxShift' => 1,
                'balanceCooldown' => 3,
            ],
        ],

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

Theo cấu hình trên thì trang quản trị của horizon là {{domain}}/horizon. Các job thành công được quản trị trong 1h, các job thất bại là 1 tuần.
Cấu hình worker process theo môi trường Môi trường của ứng dụng được khai báo trong file .env


// Production
APP_ENV=production
// Local
APP_ENV=local

Mặc định chỉ có 2 môi trường production và local. Tuy nhiên có dự án sử dụng nhiều môi trường như development, staging, production nên bạn cần thêm config cho các môi trường mới này, mỗi môi trường sẽ tương ứng với config worker process như bên dưới


'environments' => [
        'production' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default', 'email'], // các queue được giám sát
                'balance' => 'auto', // cân bẳng tải atuto
                'maxProcesses' => 4, // Max worker process = 4
                'tries' => 2, // retry lại job tối đa 2 lần
                'balanceMaxShift' => 1, // Tối đa 1 tiến trình mới được tạo ra
                'balanceCooldown' => 3, // Huỷ nó sau 3s
            ],
        ],

        'staging' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default', 'email'],
                'balance' => 'auto',
                'maxProcesses' => 4,
                'tries' => 2,
                'balanceMaxShift' => 1,
                'balanceCooldown' => 3,
            ],
        ],

        'development' => [
            'supervisor-1' => [
                'connection' => 'redis',
                'queue' => ['default', 'email'],
                'balance' => 'auto',
                'maxProcesses' => 4,
                'tries' => 2,
                'balanceMaxShift' => 1,
                'balanceCooldown' => 3,
            ],
        ],

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

Môi trường local có max 3 worker process. Môi trường production, staging, development, có max 4 worker process, các job có queue name là default, email sẽ được giám sát, retry 2 lần, cân bằng tải tự động(nếu queue “email” rỗng, nhưng queue “default” lại đầy, Horizon sẽ cho phép nhiều workers tới queue “default” cho đến khi queue hết job, giúp cho các jobs được xử lý nhanh hơn.)...

Các command hay dùng khi sử dụng horizon


// Horizon sẽ khởi động worker theo config file, ứng với môi trường của bạn
php artisan horizon

// Kiểm tra trạng thái của horizon process
php artisan horizon:status

// Dừng horizon process
php artisan horizon:pause

// Tiếp tục horizon process
php artisan horizon:continue

// Dùng khi cần update code trong job, worker sẽ thực hiện hết các job và thoát ra.
php artisan horizon:terminate

// Xoá job failed
php artisan horizon:forget 5

// Xoá all job trong queue
php artisan horizon:clear

// Build lại trang quản trị horizon
php artisan horizon:publish

Giám sát horizon

Việc giám sát horizon cũng giống như giám sát php artisan queue:worker cần sử dụng supervisor. Cần tạo file horizon.conf trong thư mục /etc/supervisor/conf.d


[program:horizon]
process_name=%(program_name)s
command=php /var/www/html/demo/artisan horizon
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/html/demo/storage/logs/horizon.log
stopwaitsecs=3600

Start supervisor


// Đọc config mới
sudo supervisorctl reread

// Update config mới
sudo supervisorctl update

// Start supervisor horizon
sudo supervisorctl start horizon

Check process khi cài đặt xong supervisor


// Command in ubuntu
ps aux | grep horizon

// Result
www-data 11841  0.0  2.8 376332 57504 ?        S    May20   1:07 /usr/bin/php7.4 artisan horizon:work redis --name=default --supervisor=ekyc-stg-api-1-k2ph:supervisor-1 --backoff=0 --max-time=0 --max-jobs=0 --memory=128 --queue=default --sleep=3 --timeout=60 --tries=2 --rest=0
www-data 23782  0.0  2.2 291120 45928 ?        S    May13  14:10 php /var/www/html/neo-webapp/artisan horizon
www-data 23790  0.0  2.2 291132 45972 ?        S    May13  12:50 /usr/bin/php7.4 artisan horizon:supervisor ekyc-stg-api-1-k2ph:supervisor-1 redis --workers-name=default --balance=auto --max-processes=4 --min-processes=1 --nice=0 --balance-cooldown=3 --balance-max-shift=1 --parent-id=23782 --backoff=0 --max-time=0 --max-jobs=0 --memory=128 --queue=default --sleep=3 --timeout=60 --tries=2 --rest=0

Đăng nhập để vào trang quản trị

Mặc định horizon cho phép người dùng vào trang quản trị mà không cần phải đăng nhập. Tuy nhiên bạn hoàn tạo có thể bắt người dùng đăng nhập mới được vào trang quản trị: có 2 cách là sử dụng laravel authentication và basic auth.

1. Sử dụng laravel auth

Bạn chỉ cho phép người dùng có email là taylor@laravel.com được truy cập vào trang quản trị, vào file app/Providers/HorizonServiceProvider.php hàm gate thêm vào đoạn code sau là xong


/**
 * 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, [
            'taylor@laravel.com',
        ]);
    });
}

Sử dụng basic auth

Bước 1: sử lại hàm gate trong file app/Providers/HorizonServiceProvider.php


namespace App\Providers;

use Laravel\Horizon\Horizon;
use Laravel\Horizon\HorizonApplicationServiceProvider;

class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();
    }

    /**
     * Overload authorization method from \Laravel\Horizon\HorizonApplicationServiceProvider
     * to allow access to Horizon without having a logged in user.
     *
     * @return void
     */
    protected function authorization()
    {
        Horizon::auth(function ($request) {
            return true;
        });
    }
}

Bước 2: Viết basic auth horizon middleware


namespace App\Http\Middleware;

use Closure;

class HorizonBasicAuthMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $authenticationHasPassed = false;

        if ($request->header('PHP_AUTH_USER', null) && $request->header('PHP_AUTH_PW', null)) {
            $username = $request->header('PHP_AUTH_USER');
            $password = $request->header('PHP_AUTH_PW');

            if ($username === config('horizon.basic_auth.username') && $password === config('horizon.basic_auth.password')) {
                $authenticationHasPassed = true;
            }
        }

        if ($authenticationHasPassed === false) {
            return response()->make('Invalid credentials.', 401, ['WWW-Authenticate' => 'Basic']);
        }

        return $next($request);
    }
}

Đăng kí middleware trong file App\Http\Kernel.php


protected $routeMiddleware = [
    // your other Middlewares
    'horizonBasicAuth' => \App\Http\Middleware\HorizonBasicAuthMiddleware::class,
];

Bước 3: Khai báo user, pass đăng nhập basic auth và thiết lập horizon sử dụng basic auth
Khai báo user, pass đăng nhập basic auth


// config/horizon.php

return [

    // ...

    'basic_auth' => [
        'username' => env('HORIZON_BASIC_AUTH_USERNAME'),
        'password' => env('HORIZON_BASIC_AUTH_PASSWORD'),
    ],

];

Thiết lập horizon sử dụng basic auth


// config/horizon.php

/*
    |--------------------------------------------------------------------------
    | Horizon Route Middleware
    |--------------------------------------------------------------------------
    |
    | These middleware will get attached onto each Horizon route, giving you
    | the chance to add your own middleware to this list or change any of
    | the existing middleware. Or, you can simply stick with this list.
    |
    */

    'middleware' => ['web', 'horizonBasicAuth'],

Như vậy là mình đã add xong basic auth cho horizon, mỗi lần vào trang quản trị, bạn sẽ phải đăng nhập basic auth để truy cập vào trang.

Cảm ơn các bạn đã dành thời gian đọc bài viết này. Hi vọng nó sẽ giúp ích cho các bạn trong việc giám sát queue job khi sử dụng laravel framework.