這篇文章主要介紹的內容是關於Laravel 的隊列系統介紹,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
Laravel 隊列為不同的後台佇列服務提供統一的 API , 例如 Beanstalk,Amazon SQS, Redis,甚至其他基於關係型資料庫的隊列。 隊列的目的是將耗時的任務延時處理,比如發送郵件,從而大幅度縮短Web請求和相應的時間。
隊列設定檔存放在 config/queue.php
。 每一種隊列驅動的配置都可以在該檔案中找到, 包括資料庫, Beanstalkd, Amazon SQS, Redis, 以及同步(本地使用)驅動。 其中還包含了一個null
隊列驅動用於那些放棄隊列的任務。
串連 Vs. 隊列
在開始使用 Laravel 隊列前,弄明白 「串連」 和 「隊列」 的區別是很重要的。在你的 config/queue.php
設定檔裡, 有一個 connections
配置選項。 這個選項給 Amazon SQS, Beanstalk ,或者 Redis 這樣的後端服務定義了一個特有的串連。不管是哪一種,一個給定的串連可能會有多個「隊列」,而 「隊列」可以被認為是不同的棧或者大量的隊列任務。
要注意的是, queue
設定檔中每個串連的配置樣本中都包含一個 queue
屬性。這是預設隊列,任務被發給指定串連的時候會被分發到這個隊列中。換句話說,如果你分發任務的時候沒有顯式定義隊列,那麼它就會被放到串連配置中 queue
屬性所定義的隊列中:
// 這個任務將被分發到預設隊列...dispatch(new Job);// 這個任務將被發送到「emails」隊列...dispatch((new Job)->onQueue('emails'));
有些應用可能不需要把任務發到不同的隊列,而只發到一個簡單的隊列中就行了。但是把任務推到不同的隊列仍然是非常有用的,因為 Laravel 隊列處理器允許你定義隊列的優先順序,所以你能給不同的隊列劃分不同的優先順序或者區分不同任務的不同處理方式了。比如說,如果你把任務推到 high
隊列中,你就能讓隊列處理器優先處理這些任務了:
php artisan queue:work --queue=high,default
驅動的必要設定
資料庫
要使用 database
這個隊列驅動的話, 你需要建立一個資料表來儲存任務,你可以用 queue:table
這個 Artisan 命令來建立這個資料表的遷移。 當遷移建立好以後,就可以用 migrate
這條命令來建立資料表:
php artisan queue:tablephp artisan migrate
Redis
為了使用 redis
隊列驅動, 你需要在你的設定檔 config/database.php
中配置Redis的資料庫連接
如果你的 Redis 隊列串連使用的是 Redis 叢集, 你的隊列名稱必須包含 key hash tag 。 這是為了確保所有的 redis 鍵對於一個給定的隊列都置於同一雜湊中:
'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => '{default}', 'retry_after' => 90,],
其它隊列驅動的依賴擴充包
在使用列表裡的佇列服務前,必須安裝以下依賴擴充包:
Amazon SQS: aws/aws-sdk-php ~3.0
Beanstalkd: pda/pheanstalk ~3.0
Redis: predis/predis ~1.0
建立任務
產生任務類
在你的應用程式中,隊列的任務類都預設放在 app/Jobs
目錄下,如果這個目錄不存在,那當你運行 make:job
artisan 命令時目錄就會被自動建立。 你可以用以下的 Artisan 命令來產生一個新的隊列任務:
php artisan make:job SendReminderEmail
產生的類實現了 Illuminate\Contracts\Queue\ShouldQueue
介面,這意味著這個任務將會被推送到隊列中,而不是同步執行。
任務類結構
任務類的結構很簡單,一般來說只會包含一個讓隊列用來調用此任務的 handle
方法。我們來看一個樣本的任務類,這個樣本裡,假設我們管理著一個播客發布服務,在發布之前需要處理上傳播客檔案:
<?phpnamespace App\Jobs;use App\Podcast;use App\AudioProcessor;use Illuminate\Bus\Queueable;use Illuminate\Queue\SerializesModels;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Contracts\Queue\ShouldQueue;class ProcessPodcast implements ShouldQueue{ use InteractsWithQueue, Queueable, SerializesModels; protected $podcast; /** * 建立一個新的任務執行個體。 * * @param Podcast $podcast * @return void */ public function __construct(Podcast $podcast) { $this->podcast = $podcast; } /** * 運行任務。 * * @param AudioProcessor $processor * @return void */ public function handle(AudioProcessor $processor) { // Process uploaded podcast... }}
注意,在這個例子中,我們在任務類的構造器中直接傳遞了一個 Eloquent 模型。因為我們在任務類裡引用了 SerializesModels
這個
,使得 Eloquent 模型在處理任務時可以被優雅地序列化和還原序列化。如果你的隊列任務類在構造器中接收了一個 Eloquent 模型,那麼只有可識別出該模型的屬性會被序列化到隊列裡。當任務被實際運行時,隊列系統便會自動從資料庫中重新取回完整的模型。這整個過程對你的應用程式來說是完全透明的,這樣可以避免在序列化完整的 Eloquent 模式執行個體時所帶來的一些問題。
在隊列處理任務時,會調用 handle
方法,而這裡我們也可以通過 handle 方法的參數類型提示,讓 Laravel 的 服務容器 自動注入依賴對象。
{note} 像圖片內容這種位元據, 在放入隊列任務之前必須使用 base64_encode
方法轉換一下。 否則,當這項任務放置到隊列中時,可能無法正確序列化為 JSON。
分發任務
你寫好任務類後,就能通過 dispatch
輔助函數來分發它了。唯一需要傳遞給 dispatch
的參數是這個任務類的執行個體:
<?phpnamespace App\Http\Controllers;use App\Jobs\ProcessPodcast;use Illuminate\Http\Request;use App\Http\Controllers\Controller;class PodcastController extends Controller{ /** * 儲存播客。 * * @param Request $request * @return Response */ public function store(Request $request) { // 建立播客... dispatch(new ProcessPodcast($podcast)); }}
{tip} dispatch
提供了一種簡捷、全域可用的函數,它也非常容易測試。查看下 Laravel 測試文檔 來瞭解更多。
延遲分發
如果你想順延強制一個隊列中的任務,你可以用任務執行個體的 delay
方法。 這個方法是 Illuminate\Bus\Queueable
trait 提供的,而這個 trait 在所有自動產生的任務類中都是預設載入了的。對於延遲任務我們可以舉個例子,比如指定一個被分發10分鐘後才執行的任務:
<?phpnamespace App\Http\Controllers;use Carbon\Carbon;use App\Jobs\ProcessPodcast;use Illuminate\Http\Request;use App\Http\Controllers\Controller;class PodcastController extends Controller{ /** * 儲存一個新的播客。 * * @param Request $request * @return Response */ public function store(Request $request) { // 建立播客... $job = (new ProcessPodcast($podcast)) ->delay(Carbon::now()->addMinutes(10)); dispatch($job); }}
{note} Amazon SQS 佇列服務最大延遲 15 分鐘。
自訂隊列 & 串連
分發任務到指定隊列
通過推送任務到不同的隊列,你可以給隊列任務分類,甚至可以控制給不同的隊列分配多少任務。記住,這個並不是要推送任務到隊列設定檔中不同的 「connections」 裡,而是推送到一個串連中不同的隊列裡。要指定隊列的話,就調用任務執行個體的 onQueue
方法:
<?phpnamespace App\Http\Controllers;use App\Jobs\ProcessPodcast;use Illuminate\Http\Request;use App\Http\Controllers\Controller;class PodcastController extends Controller{ /** * 儲存一個新的播客。 * * @param Request $request * @return Response */ public function store(Request $request) { // 建立播客... $job = (new ProcessPodcast($podcast))->onQueue('processing'); dispatch($job); }}
分發任務到指定串連
如果你使用了多個隊列串連,你可以把任務推到指定串連。要指定串連的話,你可以調用任務執行個體的 onConnection
方法:
<?phpnamespace App\Http\Controllers;use App\Jobs\ProcessPodcast;use Illuminate\Http\Request;use App\Http\Controllers\Controller;class PodcastController extends Controller{ /** * 儲存一個新的播客。 * * @param Request $request * @return Response */ public function store(Request $request) { // 建立播客... $job = (new ProcessPodcast($podcast))->onConnection('sqs'); dispatch($job); }}
當然,你可以鏈式調用 onConnection
和 onQueue
來同時指定任務的串連和隊列:
$job = (new ProcessPodcast($podcast)) ->onConnection('sqs') ->onQueue('processing');
指定任務最大嘗試次數 / 逾時值
最大嘗試次數
在一項任務中指定最大的嘗試次數可以嘗試通過 Artisan 命令列 --tries
來設定:
php artisan queue:work --tries=3
但是,你可以採取更為精緻的方法來完成這項工作比如說在任務類中定義最大嘗試次數。如果在類和命令列中都定義了最大嘗試次數, Laravel 會優先執行任務類中的值:
<?phpnamespace App\Jobs;class ProcessPodcast implements ShouldQueue{ /** * 任務最大嘗試次數 * * @var int */ public $tries = 5;}
逾時
同樣的,任務可以啟動並執行最大秒數可以使用 Artisan 命令列上的 --timeout
開關指定:
php artisan queue:work --timeout=30
然而,你也可以在任務類中定義一個變數來設定可啟動並執行最大描述,如果在類和命令列中都定義了最大嘗試次數, Laravel 會優先執行任務類中的值:
<?phpnamespace App\Jobs;class ProcessPodcast implements ShouldQueue{ /** * 任務啟動並執行逾時時間。 * * @var int */ public $timeout = 120;}
錯誤處理
如果任務啟動並執行時候拋出異常,這個任務就自動被釋放回隊列,這樣它就能被再重新運行了。如果繼續拋出異常,這個任務會繼續被釋放回隊列,直到重試次數達到你應用允許的最多次數。這個最多次數是在調用 queue:work
Artisan 命令的時候通過 --tries
參數來定義的。更多隊列處理器的資訊可以 在下面看到 。
運行隊列處理器
Laravel 包含一個隊列處理器,當新任務被推到隊列中時它能處理這些任務。你可以通過 queue:work Artisan 命令來運行處理器。要注意,一旦 queue:work
命令開始,它將一直運行,直到你手動停止或者你關閉控制台:
php artisan queue:work
{tip} 要讓 queue:work
進程永久在後台運行,你應該使用進程監控工具,比如 Supervisor
來保證隊列處理器沒有停止運行。
一定要記得,隊列處理器是長時間啟動並執行進程,並在記憶體裡儲存著已經啟動的應用狀態。這樣的結果就是,處理器運行後如果你修改代碼那這些改變是不會應用到處理器中的。所以在你重新部署過程中,一定要 重啟隊列處理器 。
指定串連 & 隊列
你可以指定隊列處理器所使用的串連。你在 config/queue.php
設定檔裡定義了多個串連,而你傳遞給 work
命令的串連名字要至少跟它們其中一個是一致的:
php artisan queue:work redis
你可以自訂隊列處理器,方式是處理給定串連的特定隊列。舉例來說,如果你所有的郵件都是在 redis
串連中的 emails
隊列中處理的,你就能通過以下命令啟動一個只處理那個特定隊列的隊列處理器了:
php artisan queue:work redis --queue=emails
資源注意事項
精靈隊列不會在處理每個作業之前 「重新啟動」 架構。 因此,在每個任務完成後,您應該釋放任何佔用過大的資源。例如,如果你使用GD庫進行影像處理,你應該在完成後用 imagedestroy
釋放記憶體。
隊列優先順序
有時候你希望設定處理隊列的優先順序。比如在 config/queue.php
裡你可能設定了 redis
串連中的預設隊列優先順序為 low
,但是你可能偶爾希望把一個任務推到 high
優先順序的隊列中,像這樣:
dispatch((new Job)->onQueue('high'));
要驗證 high
隊列中的任務都是在 low
隊列中的任務之前處理的,你要啟動一個隊列處理器,傳遞給它隊列名字的列表並以英文逗號,間隔:
php artisan queue:work --queue=high,low
隊列處理器 & 部署
因為隊列處理器都是 long-lived 進程,如果代碼改變而隊列處理器沒有重啟,他們是不能應用新代碼的。所以最簡單的方式就是重新部署過程中要重啟隊列處理器。你可以很優雅地只輸入 queue:restart
來重啟所有隊列處理器。
php artisan queue:restart
這個命令將會告訴所有隊列處理器在執行完當前任務後結束進程,這樣才不會有任務丟失。因為隊列處理器在執行 queue:restart
命令時對結束進程,你應該運行一個進程管理器,比如 Supervisor 來自動重新啟動隊列處理器。
任務到期 & 逾時
任務到期
config/queue.php
設定檔裡,每一個隊列串連都定義了一個 retry_after
選項。這個選項指定了任務最多處理多少秒後就被當做失敗重試了。比如說,如果這個選項設定為 90
,那麼當這個任務持續執行了 90
秒而沒有被刪除,那麼它將被釋放回隊列。通常情況下,你應該把 retry_after
設定為最長耗時的任務所對應的時間。
{note} 唯一沒有 retry_after
選項的串連是 Amazon SQS。當用 Amazon SQS 時,你必須通過 Amazon 命令列來配置這個重試閾值。
隊列處理器逾時
queue:work
Artisan 命令對外有一個 --timeout
選項。這個選項指定了 Laravel
隊列處理器最多執行多長時間後就應該被關閉掉。有時候一個隊列的子進程會因為很多原因僵死,比如一個外部的 HTTP 要求沒有響應。這個 --timeout
選項會移除超出指定事件節流的僵死進程。
php artisan queue:work --timeout=60
retry_after
配置選項和 --timeout
命令列選項是不一樣的,但是可以同時工作來保證任務不會丟失並且不會重複執行。
{note} --timeout
應該永遠都要比 retry_after
短至少幾秒鐘的時間。這樣就能保證任務進程總能在失敗重試前就被殺死了。如果你的 --timeout
選項大於 retry_after
配置選項,你的任務可能被執行兩次。
隊列進程睡眠時間
當隊列需要處理任務時,進程將繼續處理任務,它們之間沒有延遲。 但是,如果沒有新的工作可用,sleep
參數決定了背景工作處理序將「睡眠」多長時間:
php artisan queue:work --sleep=3
Supervisor 配置
安裝 Supervisor
Supervisor 是一個 Linux 作業系統上的進程監視軟體,它會在 queue:listen
或 queue:work
命令發生失敗後自動重啟它們。要在 Ubuntu 安裝 Supervisor,可以用以下命令:
sudo apt-get install supervisor
{tip} 如果自己手動設定 Supervisor 聽起來有點難以應付,可以考慮使用 Laravel Forge ,它能給你的 Laravel 項目自動安裝與配置 Supervisor。
配置 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=3autostart=trueautorestart=trueuser=forgenumprocs=8redirect_stderr=truestdout_logfile=/home/forge/app.com/worker.log
這個例子裡的 numprocs
命令會要求 Supervisor 運行並監控 8 個 queue:work
進程,並且在它們運行失敗後重新啟動。當然,你必須更改 command
命令的 queue:work sqs
,以顯示你所選擇的隊列驅動。
啟動 Supervisor
當這個設定檔被建立後,你需要更新 Supervisor 的配置,並用以下命令來啟動該進程:
sudo supervisorctl rereadsudo supervisorctl updatesudo supervisorctl start laravel-worker:*
更多有關 Supervisor 的設定與使用,請參考 Supervisor 官方文檔。
處理失敗的任務
有時候你隊列中的任務會失敗。不要擔心,本來事情就不會一帆風順。 Laravel 內建了一個方便的方式來指定任務重試的最大次數。當任務超出這個重試次數後,它就會被插入到 failed_jobs
資料表裡面。要建立 failed_jobs
表的話,你可以用 queue:failed-table
命令:
php artisan queue:failed-tablephp artisan migrate
然後運行隊列處理器,在調用 queue:work 命令時你應該通過 --tries
參數指定任務的最大重試次數。如果不指定,任務就會永久重試:
php artisan queue:work redis --tries=3
清除失敗任務
你可以在任務類裡直接定義 failed
方法,它能在任務失敗時運行任務的清除邏輯。這個地方用來發一條警告給使用者或者重設任務執行的操作等再好不過了。導致任務失敗的異常資訊會被傳遞到 failed
方法:
<?phpnamespace App\Jobs;use Exception;use App\Podcast;use App\AudioProcessor;use Illuminate\Bus\Queueable;use Illuminate\Queue\SerializesModels;use Illuminate\Queue\InteractsWithQueue;use Illuminate\Contracts\Queue\ShouldQueue;class ProcessPodcast implements ShouldQueue{ use InteractsWithQueue, Queueable, SerializesModels; protected $podcast; /** * 建立一個新的任務執行個體。 * * @param Podcast $podcast * @return void */ public function __construct(Podcast $podcast) { $this->podcast = $podcast; } /** * 執行任務。 * * @param AudioProcessor $processor * @return void */ public function handle(AudioProcessor $processor) { // 處理上傳播客... } /** * 要處理的失敗任務。 * * @param Exception $exception * @return void */ public function failed(Exception $exception) { // 給使用者發送失敗通知,等等... }}
任務失敗事件
如果你想註冊一個當隊列任務失敗時會被調用的事件,則可以用 Queue::failing
方法。這樣你就有機會通過這個事件來用 e-mail 或 HipChat 通知你的團隊。例如我們可以在 Laravel 內建的 AppServiceProvider
中對這個事件附加一個回呼函數:
<?phpnamespace App\Providers;use Illuminate\Support\Facades\Queue;use Illuminate\Queue\Events\JobFailed;use Illuminate\Support\ServiceProvider;class AppServiceProvider extends ServiceProvider{ /** * 啟動任意應用程式的服務。 * * @return void */ public function boot() { Queue::failing(function (JobFailed $event) { // $event->connectionName // $event->job // $event->exception }); } /** * 註冊服務提供者。 * * @return void */ public function register() { // }}
重試失敗任務
要查看你在 failed_jobs
資料表中的所有失敗任務,則可以用 queue:failed
這個 Artisan 命令:
php artisan queue:failed
queue:failed
命令會列出所有任務的 ID、串連、隊列以及失敗時間,任務 ID 可以被用在重試失敗的任務上。例如要重試一個 ID 為 5
的失敗任務,其命令如下:
php artisan queue:retry 5
要重試所有失敗的任務,可以使用 queue:retry
並使用 all
作為 ID:
php artisan queue:retry all
如果你想刪除掉一個失敗任務,可以用 queue:forget
命令:
php artisan queue:forget 5
queue:flush
命令可以讓你刪除所有失敗的任務:
php artisan queue:flush
任務事件
使用隊列的 before
和 after
方法,你能指定任務處理前和處理後的回調處理。在這些回調裡正是實現額外的日誌記錄或者增加統計資料的好時機。通常情況下,你應該在 服務容器 中調用這些方法。例如,我們使用 Laravel 中的 AppServiceProvider:
<?phpnamespace App\Providers;use Illuminate\Support\Facades\Queue;use Illuminate\Support\ServiceProvider;use Illuminate\Queue\Events\JobProcessed;use Illuminate\Queue\Events\JobProcessing;class AppServiceProvider extends ServiceProvider{ /** * 啟動任意服務。 * * @return void */ public function boot() { Queue::before(function (JobProcessing $event) { // $event->connectionName // $event->job // $event->job->payload() }); Queue::after(function (JobProcessed $event) { // $event->connectionName // $event->job // $event->job->payload() }); } /** * 註冊服務提供者。 * * @return void */ public function register() { // }}
在 隊列
facade 中使用 looping
方法,你可以嘗試在隊列擷取任務之前執行指定的回調方法。舉個例子,你可以用閉包來復原之前已失敗任務的事務。
Queue::looping(function () { while (DB::transactionLevel() > 0) { DB::rollBack(); }});