上一篇講了Lumen配置Redis,現在來講一下,如何實現消息隊列
2、編寫任務類
2.1 任務類結構
默認情況下,應用的所有隊列任務都存放在app/Jobs
目錄。任務類非常簡單,正常情況下只包含一個當隊列處理該任務時被執行的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;//會引起jobs的沖突 //use Illuminate\Contracts\Bus\SelfHandling;//已自動實現 use Illuminate\Contracts\Queue\ShouldQueue; //class SendReminderEmail extends Job implements SelfHandling, ShouldQueue class SendReminderEmail extends Job implements ShouldQueue { //use InteractsWithQueue, SerializesModels; use 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) { sleep(5); //TODO 這里可以往數據庫表插入一條記錄,可以看出異步效果 } }
在本例中,注意我們能夠直接將Eloquent模型傳遞到對列任務的構造函數中。由於該任務使用了SerializesModels
trait,Eloquent模型將會在任務被執行是優雅地序列化和反序列化。如果你的隊列任務在構造函數中接收Eloquent模型,只有模型的主鍵會被序列化到隊列,當任務真正被執行的時候,隊列系統會自動從數據庫中獲取整個模型實例。這對應用而言是完全透明的,從而避免序列化整個Eloquent模型實例引起的問題。
handle
方法在任務被隊列處理的時候被調用,注意我們可以在任務的handle
方法中對依賴進行類型提示。Lumen服務容器會自動注入這些依賴。
出錯
如果任務被處理的時候拋出異常,則該任務將會被自動釋放回隊列以便再次嘗試執行。任務會持續被釋放知道嘗試次數達到應用允許的最大次數。最大嘗試次數通過Artisan任務queue:listen
或queue:work
上的--tries
開關來定義。關於運行隊列監聽器的更多信息可以在下面看到。
手動釋放任務
如果你想要手動釋放任務,生成的任務類中自帶的InteractsWithQueue
trait提供了釋放隊列任務的release
方法,該方法接收一個參數——同一個任務兩次運行之間的等待時間:
public function handle(Mailer $mailer){ if (condition) { $this->release(10); } }
檢查嘗試運行次數
正如上面提到的,如果在任務處理期間發生異常,任務會自動釋放回隊列中,你可以通過attempts
方法來檢查該任務已經嘗試運行次數:
public function handle(Mailer $mailer){ if ($this->attempts() > 3) { // } }
3、推送任務到隊列
默認的Lumen控制器位於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)); } }
為任務指定隊列
你還可以指定任務被發送到的隊列。
通過推送任務到不同隊列,你可以對隊列任務進行“分類”,甚至優先考慮分配給多個隊列的worker數目。這並不會如隊列配置文件中定義的那樣將任務推送到不同隊列“連接”,而只是在單個連接中發送給特定隊列。要指定該隊列,使用任務實例上的onQueue
方法,該方法有Lumen自帶的基類App\Jobs\Job
提供:
<?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 延遲任務
有時候你可能想要延遲隊列任務的執行。例如,你可能想要將一個注冊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請求變量到任務中很常見,Lumen提供了一些幫助函數讓這種實現變得簡單,而不用每次請求時手動執行映射。讓我么看一下 DispatchesJobs
trait上的dispatchFrom
方法。默認情況下,該trait包含在Lumen控制器基類中:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Http\Controllers\Controller; class CommerceController extends Controller{ /** * 處理指定訂單 * * @param Request $request * @param int $id * @return Response */ public function processOrder(Request $request, $id) { // 處理請求... $this->dispatchFrom('App\Jobs\ProcessOrder', $request); } }
該方法檢查給定任務類的構造函數並從HTTP請求(或者其它ArrayAccess
對象)中解析變量來填充任務需要的構造函數參數。所以,如果我們的任務類在構造函數中接收一個productId
變量,該任務將會嘗試從HTTP請求中獲取productId
參數。
你還可以傳遞一個數組作為dispatchFrom
方法的第三個參數。該數組用於填充所有請求中不存在的構造函數參數:
$this->dispatchFrom('App\Jobs\ProcessOrder', $request, [ 'taxPercentage' => 20, ]);
4、運行隊列監聽器
開啟任務監聽器
Lumen包含了一個Artisan命令用來運行推送到隊列的新任務。你可以使用queue:listen
命令運行監聽器:
php artisan queue:listen
每次執行控制器對應方法,執行任務寫入,然后再異步執行對應任務,后面兩次前后都相隔了5秒,因為加了sleep(5),不過執行sleep前就返回了結果!
下一篇再詳細分析具體的操作方法和處理方法