什麼是 Queue
在 Web 應用程式中,會有寄信、縮圖、對外部 API 查詢等需要數秒才能完成的處理。 若在 HTTP 請求中同步進行這些處理,使用者必須一直等到回應返回。 使用 Laravel 的 Queue,能將這些重負載處理在背景以非同步方式執行。 請求會立刻回應,實際處理由 worker process 另外執行。Queue 支援資料庫、Redis、Amazon SQS 等多種後端。
開發環境使用
sync driver 時,不透過 queue 即可立即執行 job。Queue 的設定
config/queue.php
Queue 的設定集中於config/queue.php。
以 QUEUE_CONNECTION 環境變數切換所使用的 driver。
.env 設定
資料庫 driver 的準備
使用database driver 時,需要儲存 job 的資料表。
Laravel 11 以後的新專案已預設包含 migration,
若沒有,可用以下指令建立:
Redis driver 的準備
使用redis driver 時,於 config/database.php 加入 Redis 連線設定,
並以 Composer 安裝 driver:
SQS Overflow Storage
Amazon SQS 的訊息 payload 有大小上限。 若要處理較大的 payload,可加入將超過的部分存到 cache store、只將 pointer 傳給 SQS 的設定。- 啟用
enabled時,會將大於 1MB 的 payload 存到指定的 cache store。 - 將
always設為true,則無論大小都會把所有 SQS payload 存到 cache store。 delete_after_processing會在 job 成功後刪除已儲存的 payload(預設true)。- 將
flush_on_clear設為true,執行queue:clear時會flushoverflow 用的 store。因會避免清掉一般 cache,建議搭配專用 store 使用。
建立 Job 類別
make:job 指令
以make:job Artisan 指令產生 job 類別的樣板:
app/Jobs/SendWelcomeEmail.php。
Job 類別的結構
ShouldQueue 介面,是在告訴 Laravel 這個 job 要以 queue 非同步處理。
Queueable trait 提供 job 佇列操作所需的方法。
Job 的分派
dispatch()
從 controller 或 service 將 job 送到 queue,使用dispatch():
延遲 dispatch
delay() 方法可讓 job 的執行延後指定時間。
dispatchAfterResponse()
使用dispatchAfterResponse() 會在回應 HTTP 給使用者之後立即執行 job。
在 sync driver 下也能運作,適合不需要專用 worker 的輕量用途。
分派到特定 queue
Queue Routing
若要將特定 job 類別預設導向指定 connection/queue,可在 ServiceProvider 的boot() 中使用 Queue::route()。無需為每個 job 類別加上 onQueue() / onConnection(),可集中管理。
Queue Routing 可被 job 端的
onQueue() / onConnection() 覆寫。Queue 轉發(Queue::forward())
使用 Queue::forward() 可以將 job 從某個 queue 轉發到其他 queue/connection。當你想切換 queue 基礎架構而不修改個別 job 或呼叫端程式碼時非常方便。
若 job 端已明確指定 connection,則 job 的指定會優先於轉發設定。
同步執行(用於測試/開發)
使用dispatchSync() 會跳過 queue 立即執行。
大量 dispatch
當要一次分派多個獨立的 job 時,可使用Bus facade 的 bulk() 方法。適合不需要如 batch 處理般的追蹤或 callback 的情境。
Bus::bulk() 會依所設定的 queue connection 與 queue 名稱將 job 分組,並將每組整批推入 queue,因此效率較高。
Bus::bulk() 會將 job 以批次方式送入 queue。與 batch 處理(Bus::batch())不同,不提供進度追蹤或完成 callback。適合需要簡潔地批次送出大量獨立 job 的情境。Job Chain
將 job 串成 chain 後,可依序執行多個 job。當 chain 內的某個 job 失敗時,後續的 job 就不會執行。Job Batch
使用 batch 可一次分派多個 job,並追蹤整體進度。首先建立job_batches 資料表用的 migration。
Batchable trait。
Bus::batch() 分派 batch,並可註冊完成、失敗、結束時的 callback。
Job 的處理
queue:work 指令
啟動 queue worker 來處理 job。Queue Worker 的監控選項
可組合常用選項精細控制 worker:在 Job 類別中設定重試
比起命令列選項,將設定寫在 job 類別本身有時更易於管理。將崩潰計為例外
預設情況下,因記憶體不足等原因導致 worker 行程崩潰或被強制終止而結束的嘗試,不會計入 job 的最大例外數(MaxExceptions)。若希望這些嘗試也計為例外,請在 job 類別中加上 CountCrashesAsExceptions 屬性。
以 Job Middleware 控制執行
Job middleware 可將速率限制、防止重疊執行等橫向邏輯從handle() 方法中抽離,改由 middleware() 方法宣告式地指定。由於邏輯不寫在 job 本身,多個 job 也能輕鬆重複使用相同的控制。
速率限制(RateLimited)
使用RateLimiter facade 的 for 方法定義速率限制,再將 Illuminate\Queue\Middleware\RateLimited middleware 套用到 job 上。
releaseAfter() 固定重試前的秒數,或用 dontRelease() 不再重試而直接結束。
Illuminate\Queue\Middleware\RateLimitedWithRedis。
防止重疊執行(WithoutOverlapping)
Illuminate\Queue\Middleware\WithoutOverlapping 可依任意 key 為基準,避免同一個 job 同時有多個實例在執行。適用於某項資源一次只想被一個 job 更新的情境。
releaseAfter() 指定重試間隔,或用 dontRelease() 立即刪除。由於此鎖是利用原子鎖功能實作的,建議搭配 expireAfter() 明確設定鎖的有效期,這樣即使 job 意外失敗或超時,鎖也不會一直殘留。
預設只會在同一個 job 類別內防止重疊執行。若想在不同 job 類別之間共用鎖 key,請使用
shared() 方法。抑制連續發生的例外(ThrottlesExceptions)
Illuminate\Queue\Middleware\ThrottlesExceptions 用於會呼叫外部 API 等不穩定服務的 job:當累計發生指定次數例外後,暫時停止後續執行。一般會搭配以時間為基準的嘗試限制(retryUntil())一起使用。
when() 僅將特定例外納入節流對象,或用 deleteWhen() 在遇到特定例外時直接刪除 job,做更細緻的控制。
Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis 可以更有效率地進行節流。
Job 的 Release(Release middleware)
當在特定條件下想不執行 job 而放回 queue 時,使用Release middleware 可簡潔實作。
Release::unless() 會在條件為 false 時 release。
失敗 Job 的處理
準備 failed_jobs 資料表
當 job 超過最大嘗試次數,會記錄到failed_jobs 資料表。
若沒有此表,可用以下指令建立:
失敗時的收尾
在 job 定義failed() 方法,可撰寫失敗時的收尾處理。
依例外停止重試
有些例外類型希望不再重試而直接視為失敗。在bootstrap/app.php 的 withExceptions() 中以 dontRetry 指定要對應的例外類別。
dontRetryWhen。當 closure 回傳 true,job 會立即標記為失敗、不再重試。
檢視失敗 job 清單
重試失敗 job
刪除失敗 job
常用的 queue driver
database driver
無需額外的中介軟體即可使用的簡單 driver。 會將 job 存到jobs 資料表,由 worker 輪詢並處理。
- 優點:安裝簡單,可直接沿用既有 RDBMS
- 缺點:對資料庫負擔大,不適合大量 job
redis driver
在正式環境中最常使用的高速 driver。 以記憶體運作,吞吐量高於資料庫,可處理大量 job。- 優點:高速、可擴展
- 缺點:需要準備 Redis 伺服器
以 Supervisor 進行正式運行
在正式環境中,需要一種當queue:work process 因某原因停止時能自動重啟的機制。
Linux 環境中一般使用 Supervisor。
numprocs=2 平行啟動 2 個 worker process。
設定後重新載入 Supervisor:
實務範例:以 queue 處理寄信
1
建立 job 類別
2
實作 job 的處理
3
從 controller 分派
4
啟動 worker
總結
適合使用 queue 的時機
適合使用 queue 的時機
- 寄送 email/SMS
- 圖片、影片的縮圖或格式轉換
- 向外部 API 送出請求
- 產生報表或 CSV 匯出
- Webhook 的送出
開發時的訣竅
開發時的訣竅
將
.env 設為 QUEUE_CONNECTION=sync,job 就會不經過 queue 立即執行。
不用啟動 worker 就能確認運作,開發中很方便。常用指令總覽
常用指令總覽