[TOC]
### **1、簡介**
[Laravel](http://laravelacademy.org/tags/laravel "View all posts in Laravel")?[隊列](http://laravelacademy.org/tags/%e9%98%9f%e5%88%97 "View all posts in 隊列")服務為各種不同的后臺隊列提供了統一的API。隊列允許你推遲耗時[任務](http://laravelacademy.org/tags/%e4%bb%bb%e5%8a%a1 "View all posts in 任務")(例如發送郵件)的執行,從而大幅提高web請求速度。
#### **1.1 配置**
隊列配置文件存放在`config/queue.php`。在該文件中你將會找到框架自帶的每一個隊列驅動的連接配置,包括[數據庫](http://laravelacademy.org/tags/%e6%95%b0%e6%8d%ae%e5%ba%93 "View all posts in 數據庫")、[Beanstalkd](http://kr.github.com/beanstalkd)、?[IronMQ](http://iron.io/)、?[Amazon SQS](http://aws.amazon.com/sqs)、?[Redis](http://redis.io/)以及同步(本地使用)驅動。其中還包含了一個null隊列驅動以拒絕隊列任務。
#### **1.2 隊列驅動預備知識**
##### **數據庫**
為了使用`database`隊列驅動,需要一張數據庫表來存放任務,要生成創建該表的遷移,運行[Artisan](http://laravelacademy.org/tags/artisan "View all posts in Artisan")命令`queue:table`,遷移被創建好了之后,使用`migrate`命令運行遷移:
~~~
php artisan queue:table
php artisan migrate
~~~
##### **其它隊列依賴**
下面是以上列出隊列驅動需要安裝的依賴:
* Amazon SQS:?`aws/aws-sdk-php ~3.0`
* [Beanstalkd](http://laravelacademy.org/tags/beanstalkd "View all posts in Beanstalkd"):?`pda/pheanstalk ~3.0`
* [Redis](http://laravelacademy.org/tags/redis "View all posts in Redis"):?`predis/predis ~1.0`
### **2、編寫任務類**
#### **2.1 生成任務類**
默認情況下,應用的所有隊列任務都存放在`app/Jobs`目錄。你可以使用Artisan CLI生成新的隊列任務:
~~~
php artisan make:job SendReminderEmail
~~~
該命令將會在`app/Jobs`目錄下生成一個新的類,并且該類實現了`Illuminate\Contracts\Queue\ShouldQueue`接口,告訴Laravel該任務應該被推送到隊列而不是同步運行。
#### **2.2 任務類結構**
任務類非常簡單,正常情況下只包含一個當隊列處理該任務時被執行的`handle`方法,讓我們看一個任務類的例子:
~~~
<?php
namespace App\Jobs;
use App\User;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendReminderEmail extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $user;
/**
* 創建一個新的任務實例
*
* @param User $user
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* 執行任務
*
* @param Mailer $mailer
* @return void
*/
public function handle(Mailer $mailer)
{
$mailer->send('emails.reminder', ['user' => $this->user], function ($m) {
//
});
$this->user->reminders()->create(...);
}
}
~~~
在本例中,注意我們能夠直接將[Eloquent模型](http://laravelacademy.org/post/2995.html)傳遞到對列任務的構造函數中。由于該任務使用了`SerializesModels`?trait,Eloquent模型將會在任務被執行是優雅地序列化和反序列化。如果你的隊列任務在構造函數中接收Eloquent模型,只有模型的主鍵會被序列化到隊列,當任務真正被執行的時候,隊列系統會自動從數據庫中獲取整個模型實例。這對應用而言是完全透明的,從而避免序列化整個Eloquent模型實例引起的問題。
`handle`方法在任務被隊列處理的時候被調用,注意我們可以在任務的`handle`方法中進行依賴注入。Laravel[服務容器](http://laravelacademy.org/post/2910.html)會自動注入這些依賴。
##### **出錯**
如果任務被處理的時候拋出異常,則該任務將會被自動釋放回隊列以便再次嘗試執行。任務會持續被釋放知道嘗試次數達到應用允許的最大次數。最大嘗試次數通過Artisan任務`queue:listen`或`queue:work`上的`--tries`開關來定義。關于運行隊列[監聽器](http://laravelacademy.org/tags/%e7%9b%91%e5%90%ac%e5%99%a8 "View all posts in 監聽器")的更多信息可以在[下面](http://laravelacademy.org/post/3252.html#running-the-queue-listener)看到。
##### **手動釋放任務**
如果你想要手動釋放任務,生成的任務類中自帶的`InteractsWithQueue`?trait提供了釋放隊列任務的`release`方法,該方法接收一個參數——同一個任務兩次運行之間的等待時間:
~~~
public function handle(Mailer $mailer){
if (condition) {
$this->release(10);
}
}
~~~
##### **檢查嘗試運行次數**
正如上面提到的,如果在任務處理期間發生異常,任務會自動釋放回隊列中,你可以通過`attempts`方法來檢查該任務已經嘗試運行次數:
~~~
public function handle(Mailer $mailer){
if ($this->attempts() > 3) {
//
}
}
~~~
### **3、推送任務到隊列**
默認的 Laravel 控制器位于`app/Http/Controllers/Controller.php`并使用了`DispatchesJobs`?trait。該trait提供了一些允許你方便推送任務到隊列的方法,例如`dispatch`方法:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 發送提醒郵件到指定用戶
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$this->dispatch(new SendReminderEmail($user));
}
}
~~~
##### **DispatchesJobs Trait**
當然,有時候你想要從應用中路由或控制器之外的某些地方分發任務,因為這個原因,你可以在應用的任何類中包含`DispatchesJobs`?trait,從而獲取對分發方法的訪問,舉個例子,下面是使用該trait的示例類:
~~~
<?php
namespace App;
use Illuminate\Foundation\Bus\DispatchesJobs;
class ExampleClass{
use DispatchesJobs;
}
~~~
##### **dispatch方法**
或者,你也可以使用全局的dispatch方法:
~~~
Route::get('/job', function () {
dispatch(new App\Jobs\PerformTask);
return 'Done!';
});
~~~
##### **為任務指定隊列**
你還可以指定任務被發送到的隊列。
根據任務被推送到的不同隊列,你可以對隊列任務進行“分類”,甚至優先考慮分配給多個隊列的worker數目。這并不會如隊列配置文件中定義的那樣將任務推送到不同隊列“連接”,而只是在單個連接中發送給特定隊列。要指定該隊列,使用任務實例上的?`onQueue`?方法,該方法由 Laravel 自帶的基類?`App\Jobs\Job`?中的?`Illuminate\Bus\Queueable`?trait提供:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 發送提醒郵件到指定用戶
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->onQueue('emails');
$this->dispatch($job);
}
}
~~~
#### **3.1?[延遲](http://laravelacademy.org/tags/%e5%bb%b6%e8%bf%9f "View all posts in 延遲")任務**
有時候你可能想要延遲隊列任務的執行。例如,你可能想要將一個注冊15分鐘后給消費者發送提醒郵件的任務放到隊列中,可以通過使用任務類上的`delay`方法來實現,該方法由`Illuminate\Bus\Queueable`?trait提供:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 發送提醒郵件到指定用戶
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->delay(60);
$this->dispatch($job);
}
}
~~~
在本例中,我們指定任務在隊列中開始執行前延遲60秒。
> 注意:Amazon SQS服務最大延遲時間是15分鐘。
#### **3.2 任務[事件](http://laravelacademy.org/tags/%e4%ba%8b%e4%bb%b6 "View all posts in 事件")**
##### **任務完成事件**
`Queue::after`?方法允許你在隊列任務執行成功后注冊一個要執行的回調函數。在該回調中我們可以添加日志、統計數據。例如,我們可以在Laravel內置的?`AppServiceProvider`?中添加事件回調:
~~~
<?php
namespace App\Providers;
use Queue;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Queue::after(function ($connection, $job, $data) {
//
});
}
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
//
}
}
~~~
### **4、運行隊列監聽器**
##### **啟動任務監聽器**
Laravel 包含了一個 Artisan 命令用來運行被推送到隊列的新任務。你可以使用?`queue:listen`?命令運行監聽器:
~~~
php artisan queue:listen
~~~
還可以指定監聽器使用哪個隊列連接:
~~~
php artisan queue:listen connection
~~~
注意一旦任務開始后,將會持續運行直到手動停止。你可以使用一個過程監視器如[Supervisor](http://laravelacademy.org/tags/supervisor "View all posts in Supervisor")來確保隊列監聽器沒有停止運行。
##### **隊列[優先級](http://laravelacademy.org/tags/%e4%bc%98%e5%85%88%e7%ba%a7 "View all posts in 優先級")**
你可以傳遞逗號分隔的隊列連接列表到`listen`任務來設置隊列優先級:
~~~
php artisan queue:listen --queue=high,low
~~~
在本例中,`high`隊列上的任務總是在從`low`隊列移動任務之前被處理。
##### **指定任務超時參數**
你還可以設置每個任務允許運行的最大時間(以秒為單位):
~~~
php artisan queue:listen --timeout=60
~~~
##### **指定隊列睡眠時間**
此外,可以指定輪詢新任務之前的等待時間(以秒為單位):
~~~
php artisan queue:listen --sleep=5
~~~
需要注意的是隊列只會在隊列上沒有任務時“睡眠”,如果存在多個有效任務,該隊列會持續運行,從不睡眠。
#### **4.1?Supervisor配置**
Supervisor為Linux操作系統提供的進程監視器,將會在[失敗](http://laravelacademy.org/tags/%e5%a4%b1%e8%b4%a5 "View all posts in 失敗")時自動重啟`queue:listen`或`queue:work`命令,要在Ubuntu上安裝Supervisor,使用如下命令:
~~~
sudo apt-get install supervisor
~~~
Supervisor配置文件通常存放在`/etc/supervisor/conf.d`目錄,在該目錄中,可以創建多個配置文件指示Supervisor如何監視進程,例如,讓我們創建一個開啟并監視`queue:work`進程的`laravel-worker.conf`文件:
~~~
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 --daemon
autostart=true
autorestart=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log
~~~
在本例中,`numprocs`指令讓Supervisor運行8個`queue:work`進程并監視它們,如果失敗的話自動重啟。配置文件創建好了之后,可以使用如下命令更新Supervisor配置并開啟進程:
~~~
sudo supervisord -c /etc/supervisord.conf
sudo supervisorctl -c /etc/supervisor/supervisord.conf
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
~~~
要了解更多關于Supervisor的使用和配置,查看[Supervisor文檔](http://supervisord.org/index.html)。此外,還可以使用Laravel Forge從web接口方便地自動配置和管理Supervisor配置。
#### **4.2 后臺隊列監聽器**
Artisan命令`queue:work`包含一個`--daemon`選項來強制隊列worker持續處理任務而不必重新啟動框架。相較于`queue:listen`命令該命令對CPU的使用有明顯降低:
~~~
php artisan queue:work connection --daemon
php artisan queue:work connection --daemon --sleep=3
php artisan queue:work connection --daemon --sleep=3 --tries=3
~~~
正如你所看到的,`queue:work`任務支持大多數`queue:listen`中有效的選項。你可以使用`php artisan help queue:work`任務來查看所有有效選項。
##### **后臺隊列監聽器編碼考慮**
后臺隊列worker在處理每個任務時不重啟框架,因此,你要在任務完成之前釋放資源,舉個例子,如果你在使用GD庫操作圖片,那么就在完成時使用`imagedestroy`釋放內存。
類似的,數據庫連接應該在后臺長時間運行完成后斷開,你可以使用`DB::reconnect`方法確保獲取了一個新的連接。
#### **4.3 部署后臺隊列監聽器**
由于后臺隊列worker是常駐進程,不重啟的話不會應用代碼中的更改,所以,最簡單的部署后臺隊列worker的方式是使用部署腳本重啟所有worker,你可以通過在部署腳本中包含如下命令重啟所有worker:
~~~
php artisan queue:restart
~~~
該命令會告訴所有隊列worker在完成當前任務處理后重啟以便沒有任務被遺漏。
> 注意:這個命令依賴于緩存系統重啟進度表,默認情況下,APC在CLI任務中無法正常工作,如果你在使用APC,需要在APC配置中添加`apc.enable_cli=1`。
### **5、處理失敗任務**
由于事情并不總是按照計劃發展,有時候你的隊列任務會失敗。別擔心,它發生在我們大多數人身上!Laravel包含了一個方便的方式來指定任務最大嘗試執行次數,任務執行次數達到最大限制后,會被插入到`failed_jobs`表,失敗任務的名字可以通過配置文件`config/queue.php`來配置。
要創建一個`failed_jobs`表的遷移,可以使用`queue:failed-table`命令:
~~~
php artisan queue:failed-table
~~~
運行[隊列監聽器](http://laravelacademy.org/post/3252.html#running-the-queue-listener)的時候,可以在`queue:listen`命令上使用`--tries`開關來指定任務最大可嘗試執行次數:
~~~
php artisan queue:listen connection-name --tries=3
~~~
#### **5.1?失敗任務事件**
如果你想要注冊一個隊列任務失敗時被調用的事件,可以使用`Queue::failing`方法,該事件通過郵件或[HipChat](https://www.hipchat.com/)通知團隊。舉個例子,我么可以在Laravel自帶的`AppServiceProvider`中附件一個回調到該事件:
~~~
<?php
namespace App\Providers;
use Queue;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider{
/**
* 啟動應用服務
*
* @return void
*/
public function boot()
{
Queue::failing(function ($connection, $job, $data) {
// Notify team of failing job...
});
}
/**
* 注冊服務提供者
*
* @return void
*/
public function register()
{
//
}
}
~~~
##### **任務類的失敗方法**
想要更加細粒度的控制,可以在隊列任務類上直接定義`failed`方法,從而允許你在失敗發生時執行指定動作:
~~~
<?php
namespace App\Jobs;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendReminderEmail extends Job implements SelfHandling, ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* 執行任務
*
* @param Mailer $mailer
* @return void
*/
public function handle(Mailer $mailer)
{
//
}
/**
* 處理失敗任務
*
* @return void
*/
public function failed()
{
// Called when the job is failing...
}
}
~~~
#### **[5.2](http://laravelacademy.org/tags/5-2 "View all posts in 5.2")?[重試](http://laravelacademy.org/tags/%e9%87%8d%e8%af%95 "View all posts in 重試")失敗任務**
要查看已插入到`failed_jobs`數據表中的所有失敗任務,可以使用Artisan命令`queue:failed`:
~~~
php artisan queue:failed
~~~
該命令將會列出任務ID,連接,對列和失敗時間,任務ID可用于重試失敗任務,例如,要重試一個ID為5的失敗任務,要用到下面的命令:
~~~
php artisan queue:retry 5
~~~
要重試所有失敗任務,使用如下命令即可:
~~~
php artisan queue:retry all
~~~
如果你要刪除一個失敗任務,可以使用`queue:forget`命令:
~~~
php artisan queue:forget 5
~~~
要刪除所有失敗任務,可以使用`queue:flush`命令:
~~~
php artisan queue:flush
~~~
- 序言
- 發行版本說明
- 升級指南
- 貢獻代碼
- 開始
- 安裝
- 配置
- Laravel Homestead
- 基礎
- HTTP 路由
- HTTP 中間件
- HTTP 控制器
- HTTP 請求
- HTTP 響應
- 視圖
- Blade 模板引擎
- 架構
- 一次請求的生命周期
- 應用目錄結構
- 服務提供者
- 服務容器
- 門面(Facades)
- 數據庫
- 起步
- 查詢構建器
- 遷移
- 填充數據
- Eloquent ORM
- 起步
- 關聯關系
- 集合
- 訪問器&修改器
- 序列化
- 服務
- 用戶認證
- 用戶授權
- Artisan Console
- 訂閱支付實現:Laravel Cashier
- 緩存
- 集合
- 集成前端資源:Laravel Elixir
- 加密
- 錯誤&日志
- 事件
- 文件系統/云存儲
- 哈希
- 輔助函數
- 本地化
- 郵件
- 包開發
- 分頁
- Redis
- 隊列
- Session
- Envoy Task Runner
- 任務調度
- 測試
- 驗證
- 新手入門指南
- 簡單任務管理系統
- 帶用戶功能的任務管理系統