- Laravel 提供了一個統一的佇列 API,支援多種驅動程式(資料庫、Redis、SQS 等),並將連接和邏輯佇列分開。
- 作業功能允許將繁重的任務移至後台運行,並支援序列化模型、中間件、一次性作業、加密、批次和字串。
- 使用 queue:work、Supervisor 和 queue:pause/queue:continue 等指令來管理工作進程,控制重試、逾時和優先權。
- 有一個完整的故障、監控和測試生態系統(failed_jobs、queue:retry、queue:monitor、Queue::fake),使生產環境中的佇列可靠。
在 Laravel 中,對於任何稍微嚴肅一點的項目來說,使用隊列幾乎是必不可少的:發送電子郵件、處理 CSV 文件、生成 PDF、調整圖像大小、調用外部 API……如果所有這些操作都在主 HTTP 請求中完成,網站就會變慢,出現 500 錯誤,用戶最終只能絕望地盯著空白屏幕。
好消息是,Laravel 提供了一個強大而靈活的佇列基礎架構,允許你將繁重的任務委託給後台進程,按優先順序管理它們,將它們分組,監控它們,在失敗時重試,甚至可以暫時暫停工作進程進行維護。讓我們仔細看看(不遺漏任何細節),如何充分利用它,包括如何在生產環境中暫停、恢復和控制其行為。
基本概念:連接、佇列和驅動程式
在 Laravel 中,暫停任何操作之前,你需要了解以下兩者之間的差異: 隊列連接和隊列本身。 在 config/queue.php 你有一個數組 connections 在這裡定義每個後端: 資料庫、Redis、Beanstalkd、SQS、同步、空值、故障轉移、RabbitMQ (如果您添加的話),等等。
每個連線可以有多個 邏輯隊列 (例如 default, emails, high, low, imports該屬性 queue 每個連線都指示如果沒有指定其他佇列時,作業將進入哪個預設佇列。 onQueue()這樣你就可以做到以下幾點:
- 快速排隊 用於電子郵件或短時交易。
- 另一個低優先權項目 適用於處理影片或大型報告等長時間任務。
- 按任務類型劃分的佇列:
imports,notificaciones,reportes等等。
當你啟動 worker 時 php artisan queue:work預設情況下,它監聽指定的佇列。 默認 在選定的連線上。如果需要設定優先權,可以傳遞多個以逗號分隔的佇列:
php artisan queue:work redis --queue=high,default,low
這樣,工作進程會先處理高優先級任務,然後處理預設任務,最後處理低優先級任務,這在系統負載較高時至關重要,因為你不希望一個 30 分鐘的報告阻塞高優先級電子郵件。
佇列驅動因素和先決條件
Laravel 為多個佇列驅動程式提供了統一的 API ,但每個驅動程式都有其特定的功能和相依性。在討論暫停工作進程之前,選擇合適的驅動程式至關重要。
驅動程式資料庫
司機 數據庫 它將作業儲存在 SQL 表中(通常 jobs它易於設置,非常適合小型專案或開發環境,在這些環境中,您不想使用 Redis 或 SQS。
基本步驟:
- 產生遷移:
php artisan queue:table(或在新版本中,它以以下形式出現)create_jobs_table有缺陷的)。 - 運行遷移:
php artisan migrate. - 配置 隊列連線=資料庫 在
.env並確認config/queue.php這'default' => env('QUEUE_CONNECTION','database').
它既方便又足夠強大,但是當隊列很多、流量很大時,它可能會成為瓶頸,因為所有資料都透過同一個關聯式資料庫傳輸。
驅動程式 Redis
Redis驅動程式是生產環境中的標準主力驅動程式。它速度快,支援高級功能(鎖定、速率限制、原子鎖、Horizon 等),並且可擴展性非常好。
要點:
- 您需要在以下位置設定 Redis 連線:
config/database.php. - 選項
serializerycompression來自 Redis 它們不兼容 使用佇列驅動程式。 - 如果您使用 Redis集群佇列名稱必須包含井號(#),例如:
{default}這樣,佇列中的所有鍵都會落入同一個插槽。 - 選項
block_for它允許工作進程在等待任務時阻塞幾秒鐘,而不是持續輪詢: 節省 CPU 並減少對 Redis 伺服器的查詢。
例如:
'redis' => ,
如果你把 block_for = 0如果希望它能快速回應諸如此類的訊號,那麼工人將一直處於阻塞狀態,直到有工作空缺為止。 SIGTERM 或暫停命令。
其他驅動程式:SQS、Beanstalkd、null、故障轉移…
除了資料庫和 Redis 之外,Laravel 還支援其他系統:
- 亞馬遜SQSAWS 託管佇列,並支援 FIFO郵件分組、去重和進階可見性設定。需要 SDK。
aws/aws-sdk-php. - 豆莖使用該軟體包非常快速方便
pda/pheanstalk. - 同步執行以下任務 立即 在相同的流程中。在開發或測試中很有用,但在生產環境中,它實際上相當於完全沒有使用佇列。
- 空:一旦任務被“調度”,就立即將其丟棄。用於完全禁用隊列系統。
- 故障轉移這允許您配置多個連接,如果主連接失敗,Laravel 會嘗試將任務推送到清單中的下一個連接。這非常適合以下環境: 高可用性 至關重要。
在暫停和復原工作流程的層面上,無論驅動程式是什麼,邏輯實際上都是相同的,因為它是由 Artisan 命令和Worker類別管理的,而不是由佇列後端管理的。
作業創建和模型序列化
Un 約伯記 在 Laravel 中,它只是一個封裝了待執行任務的類,該任務會被放入佇列中。它通常使用 Artisan 生成並保存。 app/Jobs.
要創建一個:
php artisan make:job ProcessPodcast
產生的類別 實施 ShouldQueue 並且通常使用特徵 Dispatchable, InteractsWithQueue, Queueable y SerializesModels關鍵在於方法。 handle()這就是工人接到工作後要做的事。
一個特別強大的特性是Eloquent 模型的自動序列化。如果在 Job 建構函式中接受一個模型,Laravel 不會將整個物件加入佇列中,而只會加入其識別碼。當任務被處理時,它會從資料庫重建模型,並載入其關聯關係。
典型範例(以下使用偽代碼,避免逐字重複):
- 建造者收到 播客 $podcast.
- 該特徵
SerializesModels它負責保存ID。 - En
handle(AudioProcessor $processor)你負責完成繁重的工作(例如處理音訊)。
如果您不希望模型中的關係被序列化(這樣有效負載就不會很大),您可以使用 $model->withoutRelations() 或在 PHP 8 中,該屬性 # 在開發商推廣的房產上。
處理方法中的依賴注入
另一個優點是您可以 直接將服務注入處理方法中 (就像控制器一樣),因為 Laravel 的 Jobs 服務也使用了服務容器。例如,您可以要求一個 AudioProcessorHTTP 用戶端、儲存庫等,框架會負責解析它們。
如果您需要對所謂的…進行超精細控制 handle你可以用 Container::bindMethod() 在服務提供者中,您可以自己定義注入邏輯,但在大多數情況下,沒有必要把事情搞得這麼複雜。
作業中介軟體
就像路由有中間件一樣,作業也可以有中間件。 特定中介軟體 在作業執行前後運行。這大大減少了方法中的重複程式碼。 handle() 它尤其適用於:
- 限速 使用 Redis。
- 避免 重疊 涉及同一資源的作品。
- 常規例外 適用於不穩定的 API。
- 在特定條件下跳過工作(
Skip中介軟體).
中介軟體通常放置在 app/Jobs/Middleware 並從該方法返回。 middleware() 出自約伯記。概念範例:
public function middleware(): array { return ;
特殊類型的作業:獨一無二、加密且鎖定。
Laravel 可讓您將某些工作標記為 獨特 確保永遠不會有兩個相同的實例同時排隊。這是透過實作該介面來實現的。 ShouldBeUnique o ShouldBeUniqueUntilProcessing並支援定義密鑰 uniqueId() 以及一段時間 uniqueFor.
這非常適合以下情況:
- 重新計算產品的搜尋索引: 每個產品僅限一次 直到一切結束。
- 與不允許對相同資源同時要求的外部服務進行同步。
Laravel 底層使用了一種 原子鎖 快取(Redis、memcached、資料庫等)。您也可以為該鎖選擇快取驅動程式。 uniqueVia().
此外,還有接口。 ShouldBeEncrypted,你以此表明完整的工作是 進入佇列前請輸入您的程式碼在傳遞敏感資料時非常有用,因為你不希望資料以明文形式透過 Redis、SQS 或資料庫傳輸。
以不同模式調度、延遲和執行作業
一旦你定義了工作,就可以啟動它了。 Job::dispatch() 可以從控制器、事件監聽器、Artisan 指令,甚至是閉包傳遞參數。 dispatch 他們直接去找約伯的建造者。
有趣的變體:
dispatchIf()ydispatchUnless()將貨物整理成一行。delay()因此,該作業必須等到特定日期/時間才能處理。dispatchAfterResponse()oafterResponse()endispatch()閉包會將執行延遲到 HTTP 回應傳送完畢之後,但仍在同一個 PHP 進程內執行。dispatchSync()同步運行作業(不經過佇列),但重複使用同一個類別。- 特殊聯繫,例如
deferredobackground在回應之後執行,可以在同一進程中執行,也可以在新的 PHP 進程中執行。
最後,您可以使用鍊式方法指定連接和佇列。 onConnection() y onQueue()無論是在調度時還是在作業建構器中。
精細控制:重試、退避、逾時和錯誤處理
在 Laravel 中使用佇列的一個重要部分是決定重試任務的次數、兩次嘗試之間的等待時間以及何時將其視為失敗。
可用選項:
--triesenqueue:work設定該工作進程所有任務的最大嘗試次數(除非任務本身定義了最大嘗試次數)。$triesotries()).$tries或者一種方法tries()在作業類中,用於逐作業控制。retryUntil()意思是:「你可以隨意嘗試,直到某個時間點,之後就會失敗。」如果它與tries, 命令retryUntil.$maxExceptions當問題是由於反覆未處理的異常引起的時,應終止程序。$timeouto--timeout指示作業在工作進程終止子進程之前可以執行多少秒。$failOnTimeout如果逾時,則將作業標記為失敗。$backoff方法backoff()定義重試之間的等待時間,即使數值呈指數級變化。
如果某個任務拋出了一個未捕獲的異常,Laravel 將會… 釋放回尾部 直到配置的嘗試次數用盡為止。您可以透過以下方式手動控制此操作: release($segundos) o fail($exception) 當你想故意將一項任務標記為失敗。
失敗的作業、failed_jobs 表和清理
當約伯竭盡全力之後,他就會被認為 失敗的工作 Laravel 會將其插入到表中。 failed_jobs (或如果您配置了 DynamoDB,則為 DynamoDB)。此表儲存 UUID、連線、佇列、作業負載以及拋出的例外狀況。
有用的命令:
php artisan queue:failed列出失敗的案例。php artisan queue:retry <id|all>再試一次。php artisan queue:forget <id>刪除一個。php artisan queue:flush全部刪除(新版本中可選擇按小時刪除)。php artisan queue:prune-failed --hours=48自動刪除舊記錄。
在 Jobs 中,您可以定義一個方法 failed(Throwable $exception) 為了進行額外的清理工作:通知使用者、撤銷部分操作、寫入專用日誌等。請注意,這將建立一個新的作業實例來呼叫。 failed()因此,不要依賴房產變更 handle().
作業批次:將作業批次處理並用字串將它們組合起來
當您需要啟動數百或數千個相關作業時(例如,分塊匯入大型 CSV 檔案、批次處理文件、清理資料等),建議使用Laravel 的批次功能。
基本流程:
- 建立表
job_batches同php artisan queue:batches-table && php artisan migrate. - 標記將屬於批次的作業,並賦予其特性。
Batchable. - 使用
Bus::batch()發送一組帶有回呼的作業then,catchyfinally. - 透過以下方式從每個作業存取批次
$this->batch()並查看是否已取消。 - 可以在正在進行的批次中新增更多作業
$this->batch()->add().
批次與鍊式操作完美契合:你可以在批次中包含鍊式操作,也可以將批次作為更大鍊式操作的元素。當你希望多個鍊式操作「並行」運行,並在所有鍊式操作完成後觸發特定操作時,這種整合方式尤其有用。
工作執行、優先順序和部署
隊列的所有奇妙之處都源自於你擁有 一名或多名工人 運行命令 queue:work (o queue:listen(儘管後者效率較低)。這些進程是長時間運行的守護進程,它們監聽各自的佇列。
關鍵選項 queue:work:
- 連接:
php artisan queue:work redis,queue:work database等等。 - 可樂:
--queue=high,default,low確定優先次序。 - -十一:處理單一作業並退出。
- –max-jobs:處理 X 個作業並終止(以便定期重新啟動工作進程並釋放記憶體)。
- –最大時間:運轉 N 秒後退出。
- -睡覺:當沒有工作機會時等待的秒數。
- ——空時停止:清空佇列並乾淨俐落地關閉(非常適合 Docker 環境或一次性作業)。
- -暫停如前所述,每個作業的超時時間(秒)。
- -力即使應用程式處於維護模式,也要處理作業。
由於這些進程需要長時間運行,因此在生產環境中,幾乎總是使用類似 Supervisor(在 Linux 系統上)的進程監控工具來啟動 X 個工作進程,在進程崩潰時重新啟動它們,並管理日誌。一個典型的 Laravel Supervisor 配置區塊定義了:
- 命令 與
php artisan queue:work ...具體的。 - numprocs 指示要並行啟動多少個工作進程。
- 日誌路徑、使用者、自動重新啟動等。
Laravel 中的隊列暫停和恢復
接下來我們來談談維護中經常很重要的一個問題:如何在不突然停止任務、不留下未完成任務的情況下暫停佇列中的工作進程。 Laravel 提供了幾種機制來實現這一點,有些是較新的,有些是傳統的。
暫停:佇列:暫停和中斷訊號
在最新版本中,Laravel 新增了用於暫停和恢復worker 執行的特定命令:
php artisan queue:pause:告訴系統停止接受新任務,但讓正在進行的任務完成。php artisan queue:continue:從中斷的地方繼續處理新任務。
這些命令透過 Laravel 內部使用的快取機制向工作進程發送訊號。因此,即使你的實際快取佇列使用的是其他類型的緩存,正確配置快取驅動程式(例如 memcached、redis、資料庫、檔案等)也至關重要。
重要提示:暫停佇列或工作進程不會取消或刪除已排隊的作業;它只是阻止在暫停期間從佇列中移除更多作業。
調查中斷情況及其對性能的影響
每次 worker 完成任務後,Laravel 都會檢查快取中是否有重啟、暫停或繼續的訊號。這雖然開銷很小,但確實存在。如果您非常注重效能,並且確定不需要透過命令暫停或重新啟動 worker,則可以透過呼叫下列程式碼來停用此輪詢:
Queue::withoutInterruptionPolling();
甚至可以調整靜態屬性 Illuminate\Queue\Worker 停用特定觸摸(可重啟, 可暫停但是,如果你這樣做,然後嘗試使用 queue:pause o queue:restart工人們 他們不會發現的。這是一個需要仔細考慮的設計決策。
使用 Supervisor 或 Docker 手動暫停
除了 Laravel 自身的命令之外,在生產環境中,進程通常也會以間接的方式「暫停」:
- 停止主管程序 (
supervisorctl stop laravel-worker:*),這會導致工人死亡或停止工作。 - 運用 ——空時停止 在部署過程中,讓工作進程完成佇列中的任務,然後關閉進程。
- 在 Docker 中,讓容器進程與
--stop-when-empty然後它就關機了。
與...的區別 queue:pause 完全停止進程後,作業將停止處理,直到工作進程重新啟動。 內部暫停 你可以將進程保存在記憶體中,等待命令執行。 continue這對於快速回滾部署或短期維護非常有用。
資料庫事務和提交後事件
一個典型的錯誤:在資料庫事務中調度任務。如果不小心,工作進程可能會在提交作業完成之前執行任務,導致你期望的模型或資料尚未存在於資料庫中。
為了避免這種尷尬局面,你有兩個選擇:
- 激活
'after_commit' => true在佇列連線配置中,任何在交易中分派的作業都會在內部被延遲,直到已確認為止。 - 使用依作業方法
afterCommit()obeforeCommit()調度時,強制僅在特定作業中執行此行為。
如果事務被撤銷,則事務期間分派的作業將被丟棄,而不會進入佇列,這正是為了保持一致性通常所希望的。
監控、隊列清理和測試
一旦佇列真正開始運行,僅僅發布作業然後置之不理是不夠的;你應該監控和測試一切的運行情況。
實用工具和命令:
queue:clear清空特定佇列(SQS、Redis、資料庫)。queue:monitor監控特定佇列中的作業數量並觸發事件QueueBusy如果它們超過閾值。- 招募活動 如
Queue::before,Queue::after,Queue::loopingyQueue::failing掛鉤自訂邏輯(日誌、指標、回溯等)。 - 測試中的假貨 運用
Queue::fake()yBus::fake()驗證作業、字串或批次是否已分派,而無需實際執行它們。
在測驗中,您可以聲明“此作業已排隊 X 次”、“包含這些作業的鏈已按此順序調度”或“沒有特定作業排隊”,這為複雜的流程提供了很大的安全性。
整個生態系統——包括 Redis 或 SQS 等驅動程式、精心設計的作業、用於微調的中間件、批次、鍊式呼叫、重試配置、Supervisor 管理的 worker 以及諸如此類的命令—— queue:pause y queue:continue— Laravel 讓您能夠精確到毫米地掌控後台處理,從典型的電子郵件發送到具有故障轉移的分散式佇列架構,確保您的應用程式在 HTTP 層繼續快速回應,同時在底層執行繁重的工作,而不會阻塞使用者。