跳轉至內容

Laravel Horizon

簡介

在深入研究 Laravel Horizon 之前,您應該先熟悉 Laravel 的基礎 佇列服務。Horizon 在 Laravel 佇列的基礎上增加了一些額外功能,如果您還不熟悉 Laravel 提供的基礎佇列功能,可能會感到困惑。

Laravel Horizon 為您的 Laravel Redis 佇列提供了美觀的儀表盤和程式碼驅動的配置。Horizon 允許您輕鬆監控佇列系統的關鍵指標,例如任務吞吐量、執行時長和任務失敗情況。

使用 Horizon 時,所有的佇列工作程序配置都儲存在一個簡單的配置檔案中。透過將應用程式的工作程序配置定義在版本控制的檔案中,您可以在部署應用程式時輕鬆擴充套件或修改佇列工作程序。

安裝

Laravel Horizon 要求您使用 Redis 來驅動佇列。因此,您應確保應用程式的 config/queue.php 配置檔案中將佇列連線設定為 redis。目前 Horizon 不相容 Redis 叢集。

您可以使用 Composer 包管理器將 Horizon 安裝到您的專案中

1composer require laravel/horizon

安裝 Horizon 後,使用 horizon:install Artisan 命令釋出其資源

1php artisan horizon:install

配置

釋出 Horizon 資源後,其主要配置檔案將位於 config/horizon.php。此配置檔案允許您為應用程式配置佇列工作程序選項。每個配置選項都包含了其用途說明,因此請務必仔細閱讀該檔案。

Horizon 內部使用名為 horizon 的 Redis 連線。此 Redis 連線名稱是保留的,不應在 database.php 配置檔案中分配給其他 Redis 連線,也不應作為 horizon.php 配置檔案中 use 選項的值。

環境

安裝後,您應該熟悉的主要 Horizon 配置選項是 environments 配置選項。該配置選項是一個數組,包含了應用程式執行的環境,並定義了每個環境的工作程序選項。預設情況下,此條目包含 productionlocal 環境。當然,您可以根據需要自由新增更多環境。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 'maxProcesses' => 10,
5 'balanceMaxShift' => 1,
6 'balanceCooldown' => 3,
7 ],
8 ],
9 
10 'local' => [
11 'supervisor-1' => [
12 'maxProcesses' => 3,
13 ],
14 ],
15],

您還可以定義一個萬用字元環境 (*),它將在找不到其他匹配環境時被使用。

1'environments' => [
2 // ...
3 
4 '*' => [
5 'supervisor-1' => [
6 'maxProcesses' => 3,
7 ],
8 ],
9],

啟動 Horizon 時,它將使用應用程式當前執行環境對應的工作程序配置選項。通常,環境由 APP_ENV 環境變數的值決定。例如,預設的 local Horizon 環境配置為啟動三個工作程序,並自動均衡分配給每個佇列的工作程序數量。預設的 production 環境配置為最多啟動 10 個工作程序,並自動均衡分配給每個佇列的工作程序數量。

您應確保 horizon 配置檔案的 environments 部分包含了您計劃執行 Horizon 的每個 環境 的條目。

Supervisor(監督者)

正如您在 Horizon 預設配置檔案中看到的那樣,每個環境可以包含一個或多個“supervisor”。預設情況下,配置檔案將此 supervisor 定義為 supervisor-1;不過,您可以隨意命名您的 supervisor。每個 supervisor 本質上負責“監督”一組工作程序,並處理跨佇列的工作程序均衡。

如果您想定義一組需要在該環境中執行的新工作程序,可以在給定環境中新增額外的 supervisor。如果您想為應用程式使用的特定佇列定義不同的均衡策略或工作程序數量,可以選擇這樣做。

維護模式

當您的應用程式處於 維護模式 時,除非在 Horizon 配置檔案中將 supervisor 的 force 選項設定為 true,否則排隊任務將不會被 Horizon 處理。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'force' => true,
6 ],
7 ],
8],

預設值

在 Horizon 的預設配置檔案中,您會注意到一個 defaults 配置選項。此配置選項指定了應用程式 supervisor 的預設值。Supervisor 的預設配置值將被合併到每個環境的 supervisor 配置中,從而允許您在定義 supervisor 時避免不必要的重複。

儀表盤授權

Horizon 儀表盤可以透過 /horizon 路由訪問。預設情況下,您只能在 local 環境中訪問此儀表盤。不過,在您的 app/Providers/HorizonServiceProvider.php 檔案中,有一個 授權門(Authorization Gate) 定義。此授權門控制著在非本地環境中對 Horizon 的訪問。您可以根據需要自由修改此門以限制對 Horizon 安裝的訪問。

1/**
2 * Register the Horizon gate.
3 *
4 * This gate determines who can access Horizon in non-local environments.
5 */
6protected function gate(): void
7{
8 Gate::define('viewHorizon', function (User $user) {
9 return in_array($user->email, [
11 ]);
12 });
13}

替代認證策略

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

最大任務嘗試次數

在最佳化這些選項之前,請確保您熟悉 Laravel 預設的 佇列服務 和“嘗試次數(attempts)”的概念。

您可以在 supervisor 的配置中定義任務可以消耗的最大嘗試次數。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'tries' => 10,
6 ],
7 ],
8],

此選項類似於使用 Artisan 命令處理佇列時的 --tries 選項。

當使用 WithoutOverlappingRateLimited 等中介軟體時,調整 tries 選項至關重要,因為它們會消耗嘗試次數。為此,請在 supervisor 級別調整 tries 配置值,或在任務類中定義 $tries 屬性。

如果您未設定 tries 選項,Horizon 預設只會嘗試一次,除非任務類定義了 $tries,後者優先於 Horizon 配置。

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

任務超時

同樣,您可以在 supervisor 級別設定 timeout 值,它指定了工作程序在強制終止前可以執行任務的秒數。一旦終止,任務將根據您的佇列配置進行重試或標記為失敗。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...¨
5 'timeout' => 60,
6 ],
7 ],
8],

使用 auto 均衡策略時,Horizon 會將處理中的工作程序視為“掛起”,並在縮容期間超過 Horizon 超時時間後強制殺死它們。請始終確保 Horizon 超時時間大於任何任務級的超時時間,否則任務可能會在執行過程中被終止。此外,timeout 值應始終比 config/queue.php 配置檔案中定義的 retry_after 值短幾秒。否則,您的任務可能會被處理兩次。

任務退避(延遲重試)

您可以在 supervisor 級別定義 backoff 值,以指定 Horizon 在重試遇到未捕獲異常的任務之前應等待的時間。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'backoff' => 10,
6 ],
7 ],
8],

您還可以透過使用陣列來為 backoff 值配置“指數”退避。在此示例中,第一次重試將延遲 1 秒,第二次重試延遲 5 秒,第三次重試延遲 10 秒,如果還有剩餘嘗試次數,則隨後的每次重試均延遲 10 秒。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'backoff' => [1, 5, 10],
6 ],
7 ],
8],

靜默任務

有時,您可能對檢視應用程式或第三方包分發的某些任務不感興趣。為了防止這些任務佔用“已完成任務”列表的空間,您可以將它們設為靜默。首先,將任務的類名新增到應用程式 horizon 配置檔案的 silenced 配置選項中。

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

除了靜默單個任務類外,Horizon 還支援基於 標籤 靜默任務。如果您想隱藏共享同一標籤的多個任務,這將非常有用。

1'silenced_tags' => [
2 'notifications'
3],

或者,您希望靜默的任務可以實現 Laravel\Horizon\Contracts\Silenced 介面。如果任務實現了此介面,它將自動被靜默,即使它不存在於 silenced 配置陣列中。

1use Laravel\Horizon\Contracts\Silenced;
2 
3class ProcessPodcast implements ShouldQueue, Silenced
4{
5 use Queueable;
6 
7 // ...
8}

均衡策略

每個 supervisor 可以處理一個或多個佇列,但與 Laravel 的預設佇列系統不同,Horizon 允許您在三種工作程序均衡策略中進行選擇:autosimplefalse

自動均衡

auto 策略是預設策略,它根據佇列的當前工作負載調整每個佇列的工作程序數量。例如,如果您的 notifications 佇列有 1,000 個待處理任務,而 default 佇列為空,Horizon 將為 notifications 佇列分配更多工作程序,直到佇列清空。

使用 auto 策略時,您還可以配置 minProcessesmaxProcesses 配置選項。

  • minProcesses 定義了每個佇列的最少工作程序數。該值必須大於或等於 1。
  • maxProcesses 定義了 Horizon 在所有佇列中可以擴充套件到的最大總工作程序數。該值通常應大於佇列數量乘以 minProcesses 的值。為防止 supervisor 產生任何程序,您可以將此值設定為 0。

例如,您可以將 Horizon 配置為每個佇列至少保持一個程序,並最多擴充套件到總共 10 個工作程序。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 'connection' => 'redis',
5 'queue' => ['default', 'notifications'],
6 'balance' => 'auto',
7 'autoScalingStrategy' => 'time',
8 'minProcesses' => 1,
9 'maxProcesses' => 10,
10 'balanceMaxShift' => 1,
11 'balanceCooldown' => 3,
12 ],
13 ],
14],

autoScalingStrategy 配置選項決定了 Horizon 將如何為佇列分配更多工作程序。您可以在兩種策略之間進行選擇:

  • time 策略將根據清除佇列所需的預計總時間來分配工作程序。
  • size 策略將根據佇列中的任務總數來分配工作程序。

balanceMaxShiftbalanceCooldown 配置值決定了 Horizon 適應工作負載需求的響應速度。在上面的示例中,每三秒鐘最多建立一個或銷燬一個新程序。您可以根據應用程式的需求自由調整這些值。

佇列優先順序與自動均衡

使用 auto 均衡策略時,Horizon 不會強制執行佇列之間的嚴格優先順序。supervisor 配置中佇列的順序不會影響工作程序的分配方式。相反,Horizon 依賴於選定的 autoScalingStrategy 根據佇列負載動態分配工作程序。

例如,在以下配置中,儘管 high 佇列在列表中排在首位,但它並不優先於 default 佇列。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'queue' => ['high', 'default'],
6 'minProcesses' => 1,
7 'maxProcesses' => 10,
8 ],
9 ],
10],

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

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'queue' => ['default'],
6 'minProcesses' => 1,
7 'maxProcesses' => 10,
8 ],
9 'supervisor-2' => [
10 // ...
11 'queue' => ['images'],
12 'minProcesses' => 1,
13 'maxProcesses' => 1,
14 ],
15 ],
16],

在此示例中,預設的 queue 最多可擴充套件到 10 個程序,而 images 佇列被限制為一個程序。此配置確保了您的佇列可以獨立擴充套件。

分發資源密集型任務時,有時最好將它們分配給具有有限 maxProcesses 值的專用佇列。否則,這些任務可能會消耗過多的 CPU 資源並使您的系統過載。

簡單均衡

simple 策略將工作程序均勻分佈在指定的佇列中。使用此策略,Horizon 不會自動縮放工作程序的數量。相反,它使用固定數量的程序。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'queue' => ['default', 'notifications'],
6 'balance' => 'simple',
7 'processes' => 10,
8 ],
9 ],
10],

在上面的示例中,Horizon 將為每個佇列分配 5 個程序,將總共 10 個程序平均分配。

如果您想單獨控制分配給每個佇列的工作程序數量,可以定義多個 supervisor。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'queue' => ['default'],
6 'balance' => 'simple',
7 'processes' => 10,
8 ],
9 'supervisor-notifications' => [
10 // ...
11 'queue' => ['notifications'],
12 'balance' => 'simple',
13 'processes' => 2,
14 ],
15 ],
16],

透過此配置,Horizon 將為 default 佇列分配 10 個程序,為 notifications 佇列分配 2 個程序。

不均衡

balance 選項設定為 false 時,Horizon 將嚴格按照佇列在配置中列出的順序處理佇列,類似於 Laravel 的預設佇列系統。但是,如果任務開始積壓,它仍然會縮放工作程序的數量。

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'queue' => ['default', 'notifications'],
6 'balance' => false,
7 'minProcesses' => 1,
8 'maxProcesses' => 10,
9 ],
10 ],
11],

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

您可以使用 minProcessesmaxProcesses 選項來控制 Horizon 縮放工作程序的能力。

  • minProcesses 定義了總的最少工作程序數。該值必須大於或等於 1。
  • maxProcesses 定義了 Horizon 可以縮放到的最大總工作程序數。

升級 Horizon

升級到 Horizon 的新大版本時,務必仔細閱讀 升級指南

執行 Horizon

在應用程式的 config/horizon.php 配置檔案中配置好 supervisor 和工作程序後,您可以使用 horizon Artisan 命令啟動 Horizon。此命令將為當前環境啟動所有已配置的工作程序。

1php artisan horizon

您可以使用 horizon:pausehorizon:continue Artisan 命令暫停 Horizon 程序並指示其繼續處理任務。

1php artisan horizon:pause
2 
3php artisan horizon:continue

您還可以使用 horizon:pause-supervisorhorizon:continue-supervisor Artisan 命令暫停和繼續特定的 Horizon supervisor

1php artisan horizon:pause-supervisor supervisor-1
2 
3php artisan horizon:continue-supervisor supervisor-1

您可以使用 horizon:status Artisan 命令檢查 Horizon 程序的當前狀態。

1php artisan horizon:status

您可以使用 horizon:supervisor-status Artisan 命令檢查特定 Horizon supervisor 的當前狀態。

1php artisan horizon:supervisor-status supervisor-1

您可以使用 horizon:terminate Artisan 命令優雅地終止 Horizon 程序。當前正在處理的所有任務都將完成,隨後 Horizon 將停止執行。

1php artisan horizon:terminate

自動重啟 Horizon

在本地開發過程中,您可以執行 horizon:listen 命令。使用 horizon:listen 命令時,您無需在想要重新載入更新後的程式碼時手動重啟 Horizon。在使用此功能之前,請確保您的本地開發環境中已安裝 Node。此外,您還應該在專案中安裝 Chokidar 檔案監視庫。

1npm install --save-dev chokidar

安裝 Chokidar 後,您可以使用 horizon:listen 命令啟動 Horizon。

1php artisan horizon:listen

在 Docker 或 Vagrant 中執行時,您應該使用 --poll 選項。

1php artisan horizon:listen --poll

您可以使用應用程式 config/horizon.php 配置檔案中的 watch 配置選項來配置應監視的目錄和檔案。

1'watch' => [
2 'app',
3 'bootstrap',
4 'config',
5 'database',
6 'public/**/*.php',
7 'resources/**/*.php',
8 'routes',
9 'composer.lock',
10 '.env',
11],

部署 Horizon

當您準備將 Horizon 部署到應用程式的正式伺服器時,您應該配置一個程序監視器來監控 php artisan horizon 命令,並在其意外退出時將其重啟。不用擔心,我們將在下面討論如何安裝程序監視器。

在應用程式的部署過程中,您應該指示 Horizon 程序終止,以便它能被程序監視器重啟並接收您的程式碼更改。

1php artisan horizon:terminate

安裝 Supervisor

Supervisor 是 Linux 作業系統的程序監視器,如果 horizon 程序停止執行,它將自動重啟該程序。要在 Ubuntu 上安裝 Supervisor,您可以使用以下命令。如果您不使用 Ubuntu,通常可以使用作業系統的包管理器來安裝 Supervisor。

1sudo apt-get install supervisor

如果您覺得自行配置 Supervisor 有困難,可以考慮使用 Laravel Cloud,它可以為您管理 Laravel 應用程式的後臺程序。

Supervisor 配置

Supervisor 配置檔案通常儲存在伺服器的 /etc/supervisor/conf.d 目錄中。在此目錄中,您可以建立任意數量的配置檔案,以指示 Supervisor 如何監控您的程序。例如,讓我們建立一個 horizon.conf 檔案,啟動並監控 horizon 程序。

1[program:horizon]
2process_name=%(program_name)s
3command=php /home/forge/example.com/artisan horizon
4autostart=true
5autorestart=true
6user=forge
7redirect_stderr=true
8stdout_logfile=/home/forge/example.com/horizon.log
9stopwaitsecs=3600

定義 Supervisor 配置時,請確保 stopwaitsecs 的值大於您執行時間最長的任務所花費的秒數。否則,Supervisor 可能會在任務處理完成之前將其殺死。

雖然上述示例適用於基於 Ubuntu 的伺服器,但其他伺服器作業系統對 Supervisor 配置檔案的位置和副檔名的要求可能有所不同。請查閱您伺服器的文件以獲取更多資訊。

啟動 Supervisor

建立配置檔案後,您可以使用以下命令更新 Supervisor 配置並啟動受監控的程序。

1sudo supervisorctl reread
2 
3sudo supervisorctl update
4 
5sudo supervisorctl start horizon

有關執行 Supervisor 的更多資訊,請查閱 Supervisor 文件

標籤

Horizon 允許您為任務分配“標籤”,包括可郵件物件、廣播事件、通知和排隊事件監聽器。事實上,Horizon 會根據附加到任務的 Eloquent 模型智慧且自動地為大多數任務新增標籤。例如,看看下面這個任務。

1<?php
2 
3namespace App\Jobs;
4 
5use App\Models\Video;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Foundation\Queue\Queueable;
8 
9class RenderVideo implements ShouldQueue
10{
11 use Queueable;
12 
13 /**
14 * Create a new job instance.
15 */
16 public function __construct(
17 public Video $video,
18 ) {}
19 
20 /**
21 * Execute the job.
22 */
23 public function handle(): void
24 {
25 // ...
26 }
27}

如果此任務與一個 id 屬性為 1App\Models\Video 例項一起排隊,它將自動獲得 App\Models\Video:1 標籤。這是因為 Horizon 會在任務屬性中搜索任何 Eloquent 模型。如果找到 Eloquent 模型,Horizon 將使用模型的類名和主鍵智慧地為任務新增標籤。

1use App\Jobs\RenderVideo;
2use App\Models\Video;
3 
4$video = Video::find(1);
5 
6RenderVideo::dispatch($video);

手動為任務新增標籤

如果您想手動為您的某個可佇列物件定義標籤,可以在類上定義一個 tags 方法。

1class RenderVideo implements ShouldQueue
2{
3 /**
4 * Get the tags that should be assigned to the job.
5 *
6 * @return array<int, string>
7 */
8 public function tags(): array
9 {
10 return ['render', 'video:'.$this->video->id];
11 }
12}

手動為事件監聽器新增標籤

檢索排隊事件監聽器的標籤時,Horizon 會自動將事件例項傳遞給 tags 方法,允許您將事件資料新增到標籤中。

1class SendRenderNotifications implements ShouldQueue
2{
3 /**
4 * Get the tags that should be assigned to the listener.
5 *
6 * @return array<int, string>
7 */
8 public function tags(VideoRendered $event): array
9 {
10 return ['video:'.$event->video->id];
11 }
12}

通知

配置 Horizon 傳送 Slack 或 SMS 通知時,您應檢視 相關通知渠道的先決條件

如果您希望在隊列出現長時間等待時收到通知,可以使用 Horizon::routeMailNotificationsToHorizon::routeSlackNotificationsToHorizon::routeSmsNotificationsTo 方法。您可以從應用程式的 App\Providers\HorizonServiceProviderboot 方法中呼叫這些方法。

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 parent::boot();
7 
8 Horizon::routeSmsNotificationsTo('15556667777');
9 Horizon::routeMailNotificationsTo('[email protected]');
10 Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
11}

配置通知等待時間閾值

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

1'waits' => [
2 'redis:critical' => 30,
3 'redis:default' => 60,
4 'redis:batch' => 120,
5],

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

指標

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

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('horizon:snapshot')->everyFiveMinutes();

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

1php artisan horizon:clear-metrics

刪除失敗任務

如果您想刪除失敗的任務,可以使用 horizon:forget 命令。horizon:forget 命令接受失敗任務的 ID 或 UUID 作為其唯一引數。

1php artisan horizon:forget 5

如果您想刪除所有失敗的任務,可以向 horizon:forget 命令提供 --all 選項。

1php artisan horizon:forget --all

從佇列中清除任務

如果您想從應用程式的預設佇列中刪除所有任務,可以使用 horizon:clear Artisan 命令。

1php artisan horizon:clear

您可以提供 queue 選項以從特定佇列中刪除任務。

1php artisan horizon:clear --queue=emails