1、概述
在Web開發中,我們經常會遇到需要批量處理任務的情境,比如群發郵件、秒殺資格擷取等,我們將這些耗時或者高並發的操作放到隊列中非同步執行可以有效緩解系統壓力、提高系統響應速度和負載能力。
實現隊列有多種方式,Laravel也支援多種隊列實現驅動,比如資料庫、Redis、Beanstalkd、IronMQ及Amazon SQS等,此外還支援同步方式實現隊列(預設),甚至將隊列驅動設定為null表示不使用隊列。Laravel為這些隊列驅動提供了統一的介面,從而方便我們任意切換驅動而不需要改變商務邏輯編碼,提供代碼複用性。
下面我們將以Redis驅動為例示範在Laravel如何?隊列建立、推送和執行。
2、設定檔
我們仍然從設定檔開始,首先我們需要在設定檔中配置預設隊列驅動為Redis,隊列設定檔是config/queue.php:
return [
'default' => env('QUEUE_DRIVER', 'sync'),
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'expire' => 60,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => 'localhost',
'queue' => 'default',
'ttr' => 60,
],
'sqs' => [
'driver' => 'sqs',
'key' => 'your-public-key',
'secret' => 'your-secret-key',
'queue' => 'your-queue-url',
'region' => 'us-east-1',
],
'iron' => [
'driver' => 'iron',
'host' => 'mq-aws-us-east-1.iron.io',
'token' => 'your-token',
'project' => 'your-project-id',
'queue' => 'your-queue-name',
'encrypt' => true,
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'default',
'expire' => 60,
],
],
'failed' => [
'database' => 'mysql', 'table' => 'failed_jobs',
],
];
該設定檔第一個配置項default用於指定預設的隊列驅動,這裡我們將其值改為redis(實際上是修改.env中的QUEUE_DRIVER)。
connections配置項包含了Laravel支援的所有隊列驅動,我們使用Redis驅動,所以需要配置redis項:connection對應config/database.php中redis的default配置;queue為預設隊列名稱;expire為隊列任務到期時間(秒)。這裡我們可以保持其預設配置不變。
failed配置項用於配置失敗隊列任務存放的資料庫及資料表。這裡我們需要按照自己的資料庫配置對其做相應修改。
3、編寫隊列任務
本例中,我們將示範一個給使用者發送新功能提醒郵件的例子。
首先我們通過如下Artisan命令建立任務類:
php artisan make:job SendReminderEmail --queued
--queued選項表示產生的任務類實現了ShouldQueue介面,會被推送到隊列而不是同步執行。
運行成功後會在app/Jobs目錄下產生一個SendReminderEmail.php,我們修改其內容如下:
<?php
namespace App\Jobs;
use App\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\User;
use Illuminate\Contracts\Mail\Mailer;
class SendReminderEmail extends Job implements SelfHandling, ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $user;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Execute the job.
*
* @return void
*/
public function handle(Mailer $mailer)
{
$user = $this->user;
$mailer->send('emails.reminder',['user'=>$user],function($message) use ($user){
$message->to($user->email)->subject('新功能發布');
});
}
}
這裡我們使用依賴注入引入了User和Mailer執行個體。User用於擷取使用者資訊,Mailer用於發送郵件。這裡的Mailer和前一節郵件發送中使用的Mail門面有異曲同工之效,它們最終調用的都是同一個類上的方法,這個類就是Illuminate\Mail\Mailer。
下面我們建立郵件局部視圖resources/views/emails/reminder.blade.php:
親愛的{{$user->name}},您好,Laravel學院新發布了XXX功能,立即去體驗下吧:
<a href="http://laravelacademy.org">前往學院</a>
編寫好任務類之後我們來看如何將任務推送到隊列中:
4、推送隊列任務
手動分發任務
我們可以使用控制器中的DispatchesJobs trait(該trait在控制器基類Controller.php中引入)提供的dispatch方法手動分發任務:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Mail;
use Storage;
use App\User;
use App\Jobs\SendReminderEmail;
class MailController extends Controller
{
//其他方法
//發送提醒郵件
public function sendReminderEmail(Request $request,$id){
$user = App\User::findOrFail($id);
$this->dispatch(new SendReminderEmail($user));
}
}
然後在routes.php中定義路由:
Route::get('mail/sendReminderEmail/{id}','MailController@sendReminderEmail');
運行隊列監聽器
在瀏覽器中訪問http://laravel.app:8000/mail/sendReminderEmail/1,此時任務被推送到Redis隊列中,我們還需要在命令列中運行Artisan命令執行隊列中的任務。Laravel為此提供了三種Artisan命令:
queue:work 預設只執行一次隊列請求, 當請求執行完成後就終止;
queue:listen 監聽隊列請求,只要運行著,就能一直接受請求,除非手動終止;
queue:work --daemon 同 listen 一樣, 只要運行著,就能一直接受請求,不一樣的地方是在這個運行模式下,當新的請求到來的時候,不重新載入整個架構,而是直接 fire 動作。能看出來, queue:work --daemon 是最進階的,一般推薦使用這個來處理隊列監聽。
註:使用 queue:work --daemon ,當更新代碼的時候,需要停止,然後重新啟動,這樣才能把修改的代碼應用上。
所以我們接下來在命令列中運行如下命令:
php artisan queue:work --daemon
然後去查看郵箱會收到提醒郵件:
Laravel中使用隊列實現郵件發送
註:要保證任務執行成功,需要確保users表中id為1的記錄email是一個有效郵箱。
當然你可以在控制器之外的其它地方使用dispatch分發任務,當然在此之前需要在該類中使用use DispatchesJobs。
推送任務到指定隊列
上述操作將隊列推送到預設隊列,即設定檔中的default,當然你還可以將任務推送到指定隊列:
public function sendReminderEmail(Request $request,$id){
$user = App\User::findOrFail($id);
$job = (new SendReminderEmail($user))->onQueue('emails');
$this->dispatch($job);
}
延遲任務執行
除此之外,Laravel還支援延遲任務執行時間,這裡我們指定延遲1分鐘執行任務:
public function sendReminderEmail(Request $request,$id){
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->delay(60);
$this->dispatch($job);
}
從請求中分發任務
此外,我們還可以將HTTP請求執行個體映射到任務中,然後從請求執行個體中擷取參數填充任務類的建構函式,如果請求中不包含該參數,甚至還可以傳遞額外參數,這可以通過DispatchesJobs trait提供的dispatchFrom方法來實現:
public function sendReminderEmail(Request $request,$id){
$this->dispatchFrom('App\Jobs\SendReminderEmail',$request,['id'=>$id]);
}
當然我們需要對SendReminderEmail任務類的建構函式做如下修改:
public function __construct($id)
{
$this->user = User::find($id);
}
建構函式中的$id就是從額外參數中擷取到的。