LaravelDocs(中文)

Horizon

Laravel Horizon 為 Laravel Redis 佇列提供了美觀的儀表板

簡介 (Introduction)

[!NOTE] 在深入了解 Laravel Horizon 之前,你應該先熟悉 Laravel 的基礎 佇列服務。Horizon 為 Laravel 的佇列增加了額外的功能,如果你還不熟悉 Laravel 提供的基本佇列功能,可能會感到困惑。

Laravel Horizon 為你的 Laravel Redis 佇列 提供了一個美觀的儀表板和程式碼驅動的設定。Horizon 讓你能夠輕鬆監控佇列系統的關鍵指標,例如 Job 吞吐量、執行時間和 Job 失敗情況。

使用 Horizon 時,你所有的佇列 Worker 設定都儲存在一個簡單的設定檔中。透過在版本控制的檔案中定義應用程式的 Worker 設定,你在部署應用程式時可以輕鬆擴展或修改應用程式的佇列 Worker。

安裝 (Installation)

[!WARNING] Laravel Horizon 要求你使用 Redis 來驅動你的佇列。因此,你應該確保應用程式的 config/queue.php 設定檔中的佇列連線設定為 redis

你可以使用 Composer 套件管理器將 Horizon 安裝到你的專案中:

composer require laravel/horizon

安裝 Horizon 後,使用 horizon:install Artisan 指令發布其資源:

php artisan horizon:install

設定 (Configuration)

發布 Horizon 的資源後,其主要設定檔將位於 config/horizon.php。此設定檔允許你設定應用程式的佇列 Worker 選項。每個設定選項都包含其用途的說明,因此請務必仔細閱讀此檔案。

[!WARNING] Horizon 內部使用名為 horizon 的 Redis 連線。此 Redis 連線名稱是保留的,不應指派給 database.php 設定檔中的其他 Redis 連線,也不應作為 horizon.php 設定檔中 use 選項的值。

環境 (Environments)

安裝後,你應該熟悉的主要 Horizon 設定選項是 environments 設定選項。此設定選項是一個陣列,包含你的應用程式執行的環境,並定義了每個環境的 Worker Process 選項。預設情況下,此項目包含 productionlocal 環境。但是,你可以根據需要自由添加更多環境:

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

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

你也可以定義一個萬用字元環境 (*),當找不到其他相符的環境時將使用該環境:

'environments' => [
    // ...

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

當你啟動 Horizon 時,它將使用你的應用程式目前執行環境的 Worker Process 設定選項。通常,環境由 APP_ENV 環境變數 的值決定。例如,預設的 local Horizon 環境設定為啟動三個 Worker Process,並自動平衡指派給每個佇列的 Worker Process 數量。預設的 production 環境設定為啟動最多 10 個 Worker Process,並自動平衡指派給每個佇列的 Worker Process 數量。

[!WARNING] 你應該確保 horizon 設定檔的 environments 部分包含你計劃執行 Horizon 的每個 環境 的項目。

Supervisors

正如你在 Horizon 的預設設定檔中看到的,每個環境可以包含一個或多個 "supervisors"。預設情況下,設定檔將此 supervisor 定義為 supervisor-1;但是,你可以自由地將你的 supervisors 命名為任何你想要的名稱。每個 supervisor 本質上負責 "監督" 一組 Worker Process,並負責跨佇列平衡 Worker Process。

如果你想定義一組應該在該環境中執行的新 Worker Process,你可以向給定的環境添加額外的 supervisors。如果你想為應用程式使用的給定佇列定義不同的平衡策略或 Worker Process 數量,你可以選擇這樣做。

維護模式 (Maintenance Mode)

當你的應用程式處於 維護模式 時,除非在 Horizon 設定檔中將 supervisor 的 force 選項定義為 true,否則 Horizon 不會處理佇列中的 Job:

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

預設值 (Default Values)

在 Horizon 的預設設定檔中,你會注意到一個 defaults 設定選項。此設定選項指定了應用程式 supervisors 的預設值。Supervisor 的預設設定值將合併到每個環境的 supervisor 設定中,讓你在此定義 supervisors 時避免不必要的重複。

儀表板授權 (Dashboard Authorization)

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.
 */
protected function gate(): void
{
    Gate::define('viewHorizon', function (User $user) {
        return in_array($user->email, [
            'taylor@laravel.com',
        ]);
    });
}

替代身分驗證策略 (Alternative Authentication Strategies)

請記住,Laravel 會自動將經過身分驗證的使用者注入到 Gate Closure 中。如果你的應用程式透過其他方法(例如 IP 限制)提供 Horizon 安全性,那麼你的 Horizon 使用者可能不需要 "登入"。因此,你需要將上面的 function (User $user) Closure 簽名更改為 function (User $user = null),以強制 Laravel 不要求身分驗證。

最大 Job 嘗試次數 (Max Job Attempts)

[!NOTE] 在調整這些選項之前,請確保你熟悉 Laravel 的預設 佇列服務 和 'attempts' 的概念。

你可以在 supervisor 的設定中定義 Job 可以消耗的最大嘗試次數:

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

[!NOTE] 此選項類似於使用 Artisan 指令處理佇列時的 --tries 選項。

當使用 WithoutOverlappingRateLimited 等 Middleware 時,調整 tries 選項至關重要,因為它們會消耗嘗試次數。為了處理這個問題,請在 supervisor 層級調整 tries 設定值,或在 Job 類別上定義 $tries 屬性。

如果你沒有設定 tries 選項,Horizon 預設為單次嘗試,除非 Job 類別定義了 $tries,這將優先於 Horizon 設定。

tries$tries 設定為 0 允許無限次嘗試,這在嘗試次數不確定時非常理想。為了防止無休止的失敗,你可以透過在 Job 類別上設定 $maxExceptions 屬性來限制允許的例外數量。

Job 超時 (Job Timeout)

同樣地,你可以在 supervisor 層級設定 timeout 值,它指定 Worker Process 在強制終止 Job 之前可以執行多少秒。一旦終止,Job 將根據你的佇列設定被重試或標記為失敗:

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

[!WARNING] 當使用 auto 平衡策略時,Horizon 會將執行中的 Worker 視為 "掛起",並在縮減規模期間超過 Horizon 超時後強制終止它們。始終確保 Horizon 超時大於任何 Job 層級的超時,否則 Job 可能會在執行中途被終止。此外,timeout 值應始終比 config/queue.php 設定檔中定義的 retry_after 值短幾秒。否則,你的 Job 可能會被處理兩次。

Job Backoff

你可以在 supervisor 層級定義 backoff 值,以指定 Horizon 在重試遇到未處理例外的 Job 之前應等待多長時間:

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

你也可以透過為 backoff 值使用陣列來設定 "指數" backoffs。在此範例中,第一次重試的延遲將為 1 秒,第二次重試為 5 秒,第三次重試為 10 秒,如果還有剩餘嘗試次數,則後續每次重試均為 10 秒:

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

靜音 Job (Silenced Jobs)

有時,你可能對檢視應用程式或第三方套件分派的某些 Job 不感興趣。你可以將這些 Job 靜音,而不是讓它們佔用 "Completed Jobs" 列表中的空間。要開始使用,請將 Job 的類別名稱添加到應用程式 horizon 設定檔中的 silenced 設定選項中:

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

除了靜音個別 Job 類別之外,Horizon 還支援基於 標籤 靜音 Job。如果你想隱藏共用相同標籤的多個 Job,這會很有用:

'silenced_tags' => [
    'notifications'
],

或者,你希望靜音的 Job 可以實作 Laravel\Horizon\Contracts\Silenced 介面。如果 Job 實作了此介面,即使它不存在於 silenced 設定陣列中,也會自動被靜音:

use Laravel\Horizon\Contracts\Silenced;

class ProcessPodcast implements ShouldQueue, Silenced
{
    use Queueable;

    // ...
}

平衡策略 (Balancing Strategies)

每個 supervisor 可以處理一個或多個佇列,但與 Laravel 的預設佇列系統不同,Horizon 允許你從三種 Worker 平衡策略中進行選擇:autosimplefalse

自動平衡 (Auto Balancing)

auto 策略是預設策略,它根據佇列目前的負載調整每個佇列的 Worker Process 數量。例如,如果你的 notifications 佇列有 1,000 個待處理 Job,而你的 default 佇列是空的,Horizon 將分配更多 Worker 到你的 notifications 佇列,直到佇列清空。

當使用 auto 策略時,你也可以設定 minProcessesmaxProcesses 設定選項:

  • minProcesses 定義每個佇列的最小 Worker Process 數量。此值必須大於或等於 1。
  • maxProcesses 定義 Horizon 可以跨所有佇列擴展的最大總 Worker Process 數量。此值通常應大於佇列數量乘以 minProcesses 值。為了防止 supervisor 產生任何 Process,你可以將此值設定為 0。

例如,你可以設定 Horizon 為每個佇列維持至少一個 Process,並擴展到總共 10 個 Worker Process:

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

autoScalingStrategy 設定選項決定 Horizon 如何將更多 Worker Process 指派給佇列。你可以選擇兩種策略:

  • time 策略將根據清除佇列所需的總估計時間來指派 Worker。
  • size 策略將根據佇列上的總 Job 數量來指派 Worker。

balanceMaxShiftbalanceCooldown 設定值決定 Horizon 擴展以滿足 Worker 需求的速度。在上面的範例中,每三秒鐘最多建立或銷毀一個新 Process。你可以根據應用程式的需求自由調整這些值。

佇列優先順序和自動平衡 (Queue Priorities and Auto Balancing)

當使用 auto 平衡策略時,Horizon 不會在佇列之間強制執行嚴格的優先順序。Supervisor 設定中佇列的順序不會影響 Worker Process 的指派方式。相反,Horizon 依賴選定的 autoScalingStrategy 根據佇列負載動態分配 Worker Process。

例如,在以下設定中,儘管 high 佇列在列表中排在第一位,但它並不比 default 佇列優先:

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

如果你需要在佇列之間強制執行相對優先順序,你可以定義多個 supervisors 並明確分配處理資源:

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

在此範例中,default 佇列可以擴展到 10 個 Process,而 images 佇列限制為一個 Process。此設定確保你的佇列可以獨立擴展。

[!NOTE] 當分派資源密集型 Job 時,有時最好將它們指派給具有有限 maxProcesses 值的專用佇列。否則,這些 Job 可能會消耗過多的 CPU 資源並使你的系統過載。

簡易平衡 (Simple Balancing)

simple 策略將 Worker Process 平均分配到指定的佇列。使用此策略,Horizon 不會自動擴展 Worker Process 的數量。相反,它使用固定數量的 Process:

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

在上面的範例中,Horizon 將為每個佇列指派 5 個 Process,將總共 10 個 Process 平均分配。

如果你想控制指派給每個佇列的 Worker Process 數量,你可以定義多個 supervisors:

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

透過此設定,Horizon 將指派 10 個 Process 給 default 佇列,並指派 2 個 Process 給 notifications 佇列。

無平衡 (No Balancing)

balance 選項設定為 false 時,Horizon 嚴格按照列出的順序處理佇列,類似於 Laravel 的預設佇列系統。但是,如果 Job 開始累積,它仍然會擴展 Worker Process 的數量:

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

在上面的範例中,default 佇列中的 Job 總是優先於 notifications 佇列中的 Job。例如,如果 default 中有 1,000 個 Job,而 notifications 中只有 10 個,Horizon 將在處理 notifications 中的任何 Job 之前完全處理所有 default Job。

你可以使用 minProcessesmaxProcesses 選項控制 Horizon 擴展 Worker Process 的能力:

  • minProcesses 定義總共的最小 Worker Process 數量。此值必須大於或等於 1。
  • maxProcesses 定義 Horizon 可以擴展到的最大總 Worker Process 數量。

升級 Horizon (Upgrading Horizon)

當升級到 Horizon 的新主要版本時,請務必仔細閱讀 升級指南

執行 Horizon (Running Horizon)

一旦你在應用程式的 config/horizon.php 設定檔中設定了 supervisors 和 workers,你就可以使用 horizon Artisan 指令啟動 Horizon。這個單一指令將啟動目前環境的所有已設定 Worker Process:

php artisan horizon

你可以使用 horizon:pausehorizon:continue Artisan 指令暫停 Horizon Process 並指示其繼續處理 Job:

php artisan horizon:pause

php artisan horizon:continue

你也可以使用 horizon:pause-supervisorhorizon:continue-supervisor Artisan 指令暫停和繼續特定的 Horizon supervisors

php artisan horizon:pause-supervisor supervisor-1

php artisan horizon:continue-supervisor supervisor-1

你可以使用 horizon:status Artisan 指令檢查 Horizon Process 的目前狀態:

php artisan horizon:status

你可以使用 horizon:supervisor-status Artisan 指令檢查特定 Horizon supervisor 的目前狀態:

php artisan horizon:supervisor-status supervisor-1

你可以使用 horizon:terminate Artisan 指令優雅地終止 Horizon Process。任何目前正在處理的 Job 都將完成,然後 Horizon 將停止執行:

php artisan horizon:terminate

部署 Horizon (Deploying Horizon)

當你準備好將 Horizon 部署到應用程式的實際伺服器時,你應該設定一個 Process 監視器來監視 php artisan horizon 指令,並在其意外退出時重新啟動它。別擔心,我們將在下面討論如何安裝 Process 監視器。

在應用程式的部署過程中,你應該指示 Horizon Process 終止,以便 Process 監視器重新啟動它並接收你的程式碼變更:

php artisan horizon:terminate

安裝 Supervisor (Installing Supervisor)

Supervisor 是 Linux 作業系統的 Process 監視器,如果 horizon Process 停止執行,它將自動重新啟動該 Process。要在 Ubuntu 上安裝 Supervisor,你可以使用以下指令。如果你不使用 Ubuntu,你可能可以使用作業系統的套件管理器安裝 Supervisor:

sudo apt-get install supervisor

[!NOTE] 如果自行設定 Supervisor 聽起來很麻煩,請考慮使用 Laravel Cloud,它可以管理 Laravel 應用程式的背景 Process。

Supervisor 設定 (Supervisor Configuration)

Supervisor 設定檔通常儲存在伺服器的 /etc/supervisor/conf.d 目錄中。在此目錄中,你可以建立任意數量的設定檔,指示 Supervisor 應如何監視你的 Process。例如,讓我們建立一個 horizon.conf 檔案,該檔案啟動並監視 horizon Process:

[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 的值大於最長執行 Job 所消耗的秒數。否則,Supervisor 可能會在 Job 完成處理之前將其殺死。

[!WARNING] 雖然上面的範例適用於基於 Ubuntu 的伺服器,但 Supervisor 設定檔預期的位置和副檔名可能因其他伺服器作業系統而異。請參閱你的伺服器文件以獲取更多資訊。

啟動 Supervisor (Starting Supervisor)

建立設定檔後,你可以使用以下指令更新 Supervisor 設定並啟動受監視的 Process:

sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start horizon

[!NOTE] 有關執行 Supervisor 的更多資訊,請參閱 Supervisor 文件

標籤 (Tags)

Horizon 允許你將 "tags" 指派給 Job,包括 mailables、廣播事件、通知和佇列事件監聽器。事實上,Horizon 會根據附加到 Job 的 Eloquent 模型,智慧地自動標記大多數 Job。例如,看看下面的 Job:

<?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
    {
        // ...
    }
}

如果此 Job 與具有 id 屬性為 1App\Models\Video 實例一起排入佇列,它將自動接收標籤 App\Models\Video:1。這是因為 Horizon 會搜尋 Job 的屬性以尋找任何 Eloquent 模型。如果找到 Eloquent 模型,Horizon 將使用模型的類別名稱和主鍵智慧地標記 Job:

use App\Jobs\RenderVideo;
use App\Models\Video;

$video = Video::find(1);

RenderVideo::dispatch($video);

手動標記 Job (Manually Tagging Jobs)

如果你想手動定義其中一個可排入佇列物件的標籤,你可以在類別上定義一個 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];
    }
}

手動標記事件監聽器 (Manually Tagging Event Listeners)

當檢索佇列事件監聽器的標籤時,Horizon 會自動將事件實例傳遞給 tags 方法,讓你將事件資料添加到標籤中:

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] 當設定 Horizon 發送 Slack 或 SMS 通知時,你應該查看 相關通知頻道的先決條件

如果你想在其中一個佇列有很長的等待時間時收到通知,你可以使用 Horizon::routeMailNotificationsToHorizon::routeSlackNotificationsToHorizon::routeSmsNotificationsTo 方法。你可以從應用程式的 App\Providers\HorizonServiceProviderboot 方法呼叫這些方法:

/**
 * 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)

你可以在應用程式的 config/horizon.php 設定檔中設定多少秒被視為 "長時間等待"。此檔案中的 waits 設定選項允許你控制每個連線 / 佇列組合的長時間等待閾值。任何未定義的連線 / 佇列組合將預設為 60 秒的長時間等待閾值:

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

將佇列的閾值設定為 0 將停用該佇列的長時間等待通知。

指標 (Metrics)

Horizon 包含一個指標儀表板,提供有關 Job 和佇列等待時間及吞吐量的資訊。為了填充此儀表板,你應該在應用程式的 routes/console.php 檔案中設定 Horizon 的 snapshot Artisan 指令每五分鐘執行一次:

use Illuminate\Support\Facades\Schedule;

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

如果你想刪除所有指標資料,你可以呼叫 horizon:clear-metrics Artisan 指令:

php artisan horizon:clear-metrics

刪除失敗的 Job (Deleting Failed Jobs)

如果你想刪除失敗的 Job,你可以使用 horizon:forget 指令。horizon:forget 指令接受失敗 Job 的 ID 或 UUID 作為其唯一參數:

php artisan horizon:forget 5

如果你想刪除所有失敗的 Job,你可以向 horizon:forget 指令提供 --all 選項:

php artisan horizon:forget --all

清除佇列中的 Job (Clearing Jobs From Queues)

如果你想刪除應用程式預設佇列中的所有 Job,你可以使用 horizon:clear Artisan 指令:

php artisan horizon:clear

你可以提供 queue 選項來刪除特定佇列中的 Job:

php artisan horizon:clear --queue=emails