隊列
簡介
laravel 的佇列服務對各種不同的後台佇列服務提供了統一的 API。隊列允許你順延強制消耗時間的任務,比如發送一封郵件。這樣可以有效降低請求響應的時間。
配置
隊列的設定檔被儲存在 config/queue.php 中。在這個檔案中你會發現架構所支援的隊列驅動的配置串連樣本。這些驅動包括:資料庫,Beanstalkd,Amazon SQS,Redis,和一個同步(本地使用)的驅動。
還有一個名為 null 的驅動表明不使用隊列任務。
隊列先決條件
資料庫
如果使用 database 隊列驅動,你需要添加一個資料表來處理隊列任務。你可以使用 queue:table Artisan 命令來產生一個遷移表。一旦該遷移表產生完成,你就可以使用 migrate 命令來遷移到資料庫中:
php artisan queue:tablephp artisan migrate
其他隊列依賴
下面列出了其它隊列驅動及其相應的依賴:
- Amazon SQS: aws/aws-sdk-php ~3.0
- Beanstalkd: pda/pheanstalk ~3.0
- Redis: predis/predis ~1.0
編寫任務類
產生任務類
預設的,所有的可隊列執行的任務都被儲存在 app/Jobs 目錄下,你可以通過 Artisan 命令來產生一個新的隊列任務:
php artisan make:job SendReminderEmail
該命令會在 app/Jobs 目錄下產生一個新的類。該類會實現 Illuminate\Contracts\Queue\ShouldQueue 介面,該介面表明 laravel 應該將該任務添加到背景任務隊列中,而不是同步執行。
任務類結構
任務類是十分簡單的,通常它只包含一個 handle 方法來在隊列任務執行時被調用。我們來看一個簡單的任務樣本:
user = $user; } /** * Execute the job. * * @param Mailer $mailer * @return void */ public function handle(Mailer $mailer) { $mailer->send('emails.reminder', ['user' => $this->user], function () { // }); $this->user->reminders()->create(...); }}
在這個例子中,你需要注意的是,我們可以直接的在隊列任務的建構函式中傳送一個 Eloquent 模型。因為我們引入了 SerializesModels trait,所以當隊列任務執行時,Eloquent 模型會被優雅的序列化和還原序列化。如果隊列任務在構造器中接收了 Eloquent 模型,那麼隊列任務只會序列化模型的 ID。而在任務需要進行處理時,隊列系統會從資料庫中自動的根據 ID 檢索出模型執行個體。這在應用中完全是透明的,這樣就可以避免了序列化完整的模型可能在隊列中出現的問題。
handle 方法會在隊列任務執行時進行調用。你需要知道的是,我們可以在任務的 handle 方法中可以使用類型提示來進行依賴的注入。laravel 的服務容器會自動的將這些依賴注入進去。
有異常發生
如果任務進行的過程中有異常被拋出。它會自動的將任務釋放,同時追加到隊列的尾端以使任務可以進行再次嘗試。該任務會被持續釋放進行嘗試除非嘗試的次數超出了所設定的最大次數。你可以在隊列監聽器 queue:listen 或者 queue:work Artisan 任務命令中添加 --tries 選項來設定最大可嘗試次數。我們將在後續篇幅中詳細的介紹隊列監聽器。
手動的釋放任務到隊列尾端
你可以使用 release 方法來進行手動的釋放任務。laravel 命令產生的任務類中已經引入了 InteractsWithQueue trait,這個性狀提供了訪問任務隊列的 release 方法。該方法接收一個參數:你希望任務再次可用的間隔(秒):
public function handle(Mailer $mailer){ if (condition) { $this->release(10); }}
檢查任務已嘗試的次數
就如上面我們所談到的,如果在任務進行的過程中,異常出現,那麼隊列會自動的釋放任務並將任務推送到隊列的尾端使其可以進行再次嘗試。你可以通過 attempts 方法來檢查任務的釋放次數:
public function handle(Mailer $mailer){ if ($this->attempts() > 3) { // }}
推送任務到隊列
位於 app/Http/Controllers/Controller.php 的 laravel 基礎控制器使用了 DispatchesJobs trait。這個性狀提供了一些方法允許你方便的推送任務到隊列。比如 dispatch 方法:
dispatch(new SendReminderEmail($user)); }}
DispatchesJobs 性狀
當然,有時候你可能希望在應用中的任何地方進行任務的分發,而不僅僅只在路由或者控制器中。出於這個原因,你可以使用 DispatchesJobs trait 到任何你需要在應用中調用分發任務的類中,這樣你就可以在這個類中使用 dispatch 方法,下面給出一個簡單的使用 trait 的樣本:
dispatch 方法
或者,你可以使用 dispatch 全域協助方法:
Route::get('/job', function () { dispatch(new App\Jobs\PerformTask); return 'Done!';}); 對任務指定隊列
你也可以指定任務需要被分配到的隊列。
你可以對你的隊列任務進行分類,來將任務推送到不同的隊列中,你甚至是可以分配各種隊列工作的優先權。這並不是推送任務到隊列設定檔中所定義的不同的隊列串連上,而是僅在單個串連中指定的隊列進行的操作。你可以使用任務執行個體的 onQueue 方法來指定隊列。onQueue 來自 Illuminate\Bus\Queueable trait,該性狀已經被 App\Jobs\Job 基類所引入:
onQueue('emails'); $this->dispatch($job); }} 延遲任務
有時候,你可能會想要順延強制隊列任務。比如,你可能希望有一個隊列任務可以在使用者註冊後的 5 分鐘發送一封提醒郵件。你可以在任務類中使用 delay 方法來完成,該方法是通過 Illuminate\Bus\Queueable 性狀提供的:
delay(60 * 5); $this->dispatch($job); }}
在這個例子中,我們指定了任務應該在隊列中延遲 5 分鐘執行。
注意: Amazon SQS 服務有最大順延強制限制,其最多可延遲 15 分鐘。
任務事件
任務生命週期事件
你可以使用 Queue::before 和 Queue::after 方法來註冊一個回調在隊列任務開始之前或者隊列執行成功之後調用。回調為添加額外的日誌,持續執行子任務或者增加統計資訊等提供了非常好的機會。比如,我們可以在 laravel 的 AppServiceProvider 中定義一個任務成功執行後的事件監聽,並附加一個回調到事件中:
connectionName // $event->job // $event->data }); } /** * Register the service provider. * * @return void */ public function register() { // }} 運行隊列監聽器
開始進行隊列監聽
laravel 包含了一個 Artisan 命令來運行推送到隊列中的任務的執行。你可以使用 queue:listen 命令來運行監聽器:
php artisan queue:listen
你也可以指定監聽哪一個串連的隊列:
php artisan queue:listen connection-name
你需要注意的是,這個命令執行,它會持續的運行,除非你手動的進行停止。你可以通過使用 Supervisor 進程監控來確保隊列監聽器的運行。
隊列優先順序
你可以通過使用 , 來分割串連的隊列,以確定隊列的運行優先順序:
php artisan queue:listen --queue=high,low
在這個例子中,任務會優先執行 high 的隊列,然後才會運行 low 隊列中的任務。
指定任務的逾時時間
你可以設定任務的逾時時間:
php artisan queue:listen --timeout=60
指定隊列的睡眠時間
另外,你可以指定隊列輪詢新的任務需要等待的時間(秒):
php artisan queue:listen --sleep=5
你需要注意的是,隊列只會在隊列中沒有需要執行的任務時才會進行睡眠。如果隊列中有多個可執行檔任務,那麼隊列會持續的進行任務的執行,而不會進行睡眠操作。
執行隊列的首個任務
你可以使用 queue:work 命令來只執行隊列的首個任務:
php artisan queue:work
Supervisor 配置
Supervisor 是 Linux 作業系統的一個進程監控器,並且它可以自動的在 queue:listen 或者 queue:work 命令失敗時進行重啟。你可以使用下面的命令在 Ubuntu 中安裝 Supervisor:
sudo apt-get install supervisor
Supervisor 設定檔通常都儲存在 /etc/supervisor/conf.d 目錄中。在這個目錄中,你可以建立任意數量的設定檔來指導 supervisor 來管理監控進程。比如,讓我們建立一個 laravel-worker.conf 檔案來開始和監控 queue:work 進程:
[program:laravel-worker]process_name=%(program_name)s_%(process_num)02dcommand= php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 --daemonautostart=trueautorestart=trueuser=forgenumprocs=8redirect_stderr=truestdout_logfile=/home/forge/app.com/worker.log
上面的例子中,numprocs 會指導 Supervisor 運行 8 個 queue:work 進程並且對其進行監控,它會自動的在其失敗時進行重啟。當然,你可以修改命令的 queue:work sqs 部分來使用你所期望的隊列驅動。
一旦設定檔建立完成,你可以使用下面的命令來更新 Supervisor 的設定檔和啟動進程:
sudo supervisorctl rereadsudo supervisorctl updatesudo supervisorctl start laravel-worker:*
關於更多的 Supervisor 的配置資訊,請參考 Supervisor documentation。另外,你也可以使用 Laravel Forge 來從一個方便的 Web 介面自動的配置和管理你的 Supervisor 配置。
隊列監控器守護進程
queue:work Artisan 命令包含了一個 --daemon 選項來強迫隊列工作持續的執行任務而不重新引導架構。這相比較 queue:lsten 命令來說會顯著的減少 CPU 的消耗:
php artisan queue:work connection-name --daemonphp artisan queue:work connection-name --daemon --sleep=3php artisan queue:work connection-name --daemon --sleep=3 --tries=3
就如你所看到的,queue:work 任務支援和 queue:listen 差不多的選項。你可以通過使用 php artisan help queue:work 命令來顯示可用選項。
對於守護進程的編碼注意事項
隊列任務的守護進程不會在每個任務執行之前重新引導架構,所以,你應該注意在你的任務完成時清除一些比較重的資源消耗。比如,如果你使用 GD 類庫來處理圖片,你應該在任務執行完成後使用 imagedestroy 來將其從記憶體中進行釋放。
部署具有守護進程的隊列監聽器
因為隊列工作的守護進程是一個常駐進程。它不會再你的代碼改變時進行重啟。所以,你應該使用部署指令碼來在代碼變更時重新部署使用守護進程的隊列工作:
php artisan queue:restart
該命令會優雅的指導隊列完成當前的任務後死亡,所以不會存在任務的遺漏。你應該注意的是,當你執行 queue:restart 命令時,隊列工作就會死亡,所以你應該使用一種進程管理器,比如 Supervisor,你可以配置它來自動的重啟隊列工作。
注意:該命令依賴於緩衝系統來制定重啟時間表。預設的 APCu 不支援在 CLI 中運行任務。如果你使用 APCu,你應該添加 apc.enable_cli=1 到你的 APCu 配置中。
與失敗的任務進行互動
由於很多事情並不能如計劃中的那樣進行,有時候隊列任務的執行可能會失敗,不要擔心,它發生對我們來說是最好的!laravel 包含了一種便捷的方式來指定任務應該重複嘗試的次數。如果任務被重複執行到指定的次數,它就會被記錄到 failed_jobs 表中。這個表的名字你可以通過 config/queue.php 設定檔進行定製。
你可以使用 queue:failed-table 命令來產生一個 failed_jobs 遷移表:
php artisan queue:failed-table
當你運行隊列監聽器時,你可以使用 --tries 選項來指定任務的最大嘗試次數:
php artisan queue:listen connection-name --tries=3
失敗任務事件
如果你希望在隊列任務失敗時執行某些操作,你可以使用 Queue::failing 方法來註冊一個監聽事件。這個事件對通過 email 或者 HipChat 通知你的團隊提供了一個很好的機會。比如,你可以在 AppServiceProvider 在事件中附加一個回調:
job // $event-data }); } /** * Register the service provider. * * @return void */ public function register() { // }} 任務類中的失敗方法
你可以在任務類中定義一個 failed 方法來進行更為精細的控制,這允許你在任務出現失敗時來執行一些指定的動作:
重新執行失敗的任務
你可以使用 queue:failed Artisan 命令來顯示資料庫 failed_jobs 表中失敗的任務:
php artisan queue:failed
queue:failed 命令會列出任務的 ID,串連,隊列,和失敗時間。任務 ID 可以用來進行失敗任務的嘗試。比如,你可以嘗試重新執行 ID 為 5 的任務:
php artisan queue:retry 5
你可以使用 queue:retry all 命令來重啟所有的任務:
php artisan queue:retry all
如果你希望刪除失敗的任務,你可以使用 queue:forget 命令:
php artisan queue:forget 5
你可以使用 queue:flush 命令來清除所有失敗的任務:
php artisan queue:flush