Laravel佇列詳解
1.佇列的應用場景:
PHP在非同步程式設計上的短板是眾所周知的,這也是當年PHP能夠迅速火起來的一個重要特性,當然,這也是Nodejs能夠火起來的一個相反方向的重要特性(Nodejs的亮點就是高併發的處理效能,只是安全性嘛,仁者見智了),PHP為保持語言的簡單,設計之初不考慮多執行緒,直至PHP7,有了TSRM這樣的多執行緒併發處理機制,PHP 預設依然不支援多執行緒,如果一定要使用多執行緒,還需要安裝 pthread 擴充套件。儘管併發處理這塊一致是PHP的短板,同時,大併發處理的相關技術在PHP領域好像也是小妾生的兒子般不受待見,但是並不代表需求中就不需要這些東西,於是乎就有了併發的另外一種替代方案,就是我們今天所要瞎掰的,佇列。
可見,佇列就是為了在某種程度上替代多執行緒而設計的一種處理併發的方式,同時,也就具備天生的秉性:非同步!用於處理耗時的工作,比如一個流程走到一個地方,要傳送一封郵件通知,那我不能讓整個程式等到郵件發完了再繼續下去,那我就把發郵件的工作丟到一個佇列中去,讓這個工作慢慢做,我繼續處理響應的邏輯。整個過程其實也就這麼回事,沒有很困難。
2.laravel中的佇列的實現
閒話不扯,現在有了laravel,我們不用自己設計佇列的實現機制,我們來看看怎麼用就好了。
1)首先,我們要先弄清楚幾個基本概念,我們先來想想,我們要把一項一項的工作任務放到佇列裡,最樸素的想法,我們會怎麼做?我們是不是先得給任務起個名字,然後至少得給它一段執行的程式碼吧,然後再把這一個任務作為一個整體的資料結構放到一個叫做佇列的資料結構中去,那要把名字和執行的程式碼關聯起來,肯定是用鍵值對的方式最方便了,所以,怎麼做呢?最簡單就是用陣列嘍,一個數組,然後中間一堆鍵值對,鍵名是任務名,鍵值是任務的執行程式碼。但是陣列有一個問題,陣列的檢索是一個完了接一個,我沒法控制它中間執行的非同步操作啊?但是它能夠模仿出這些操作模式,所以,在laravel中,就有了一個驅動名,叫sync,同步嘛,用來做測試用的。
2)但是,如果執行程式碼太大,然後任務太多,怎麼辦?都擺到數組裡總是不合適吧?那我們是不是就得考慮把這些鍵值對給它放到資料庫裡面去?嗯,不錯,這是一種好思路,也有人這麼做,同樣的,laravel也提供資料庫做佇列的驅動,和前面一樣,反正我的目的就是把任務讓他們排隊去,只是一個一個塞到資料庫,然後一個一個取出來執行。
3)然後,就會有人想到了,我幹嘛不採用更專業的軟體來做這個事?比如redis?人家天生就是鍵值對處理的,而且和資料庫不通,redis的資料是在記憶體中的,這樣明顯速度就提上來了,我們會這麼想,laravel團隊也會這麼想,於是有了redis驅動。
4)然後,有人想到了這麼多方法,自然就有人想專業提供這些服務的,於是就有比如亞馬遜這樣的公司提供一系列的產品來支援,就有了SQS這些驅動,等等等等。。。。
其實說白了,就是把任務的鍵名和鍵值儲存起來而已,儲存的媒介用什麼都可以,而所謂的佇列驅動,就是把儲存在這些媒介中的任務程式碼拿住來按照佇列的演算法進行執行的一種調配方式的一段程式碼而已 。
3.講完了這些,我們來看看怎麼實現
我們以redis為例,來看看laravel怎麼做佇列的:
1)一上來沒說的,安裝redis驅動:composer require 'predis/predis ~1.0'
當然,您得先有redis,如果系統沒裝,apt-get一個,yum一個隨您啦。
2)接下來,要把任務放佇列,我們得先做一個任務出來,我們寫個簡單的提交使用者資料的任務吧:
執行: php artisan make:job StoreUser
3)執行完上述命令,在App下多一個job目錄出來,然後StoreUser就在裡頭,然後我們就可以寫StoreUser的任務內容了:
<?php
namespace App\Jobs;
use ...
class StoreUser implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $user;
public function __construct(User $user)
{
$this->user=$user;
}
public function handle()
{
$this->user->name="queue";
$this->user->email="[email protected]";
$this->user->password="slcwd";
$this->user->save();
}
public function fail()
{
dd('傳送失敗');
}
}
上面這個任務很簡單,就是依賴注入一個User模型,然後在handle方法裡面寫一個使用者資料處理的方法,最後儲存,沒設麼意義,我們只是演示而已。
其實,也可以不用注入模型,比如可以寫一個服務,然後把服務注入進來,反正是個類就行。
4)接下來,我們告訴框架,要用哪個驅動,到.env下面寫這兩句話:
REDIS_QUEUE=email
這兩個配置,是配置所謂的“連結名”和“佇列名”。
所謂連結,就是我們要儲存的哪一個儲存介質,好比銀行,
佇列名,就是我們要使用哪一條佇列。
5)這樣,如果使用的是單佇列,其他就啥都不用管了,這下佇列已經可以用了。文章最後我們再來探討一個連結多個佇列的情況。
6)然後我們寫一個路由,一個控制器:
路由:Route::get('/mail','Mail\UserQueueController@queue');
控制器的方法程式碼:
public function queue()
{
$user=User::find(1);
Test::dispatch($user)->delay(10);
}
這樣,我們就可以把任務分發給隊列了。
等等。。。。。。嗯,好像我們還沒有開啟佇列,接著。。。。。。。
7)開啟佇列:
命令列中輸入:php artisan queue:work,work和linsten有啥區別,自行看手冊嘍。有空行在那閃也暫時不管它,這代表隊列已經開始工作了,只是我們還沒有把我們的工作發到佇列上,
8)現在在瀏覽器中輸入路由地址,過個10秒到資料庫中看下,1號Id的使用者資料就被修改了。
以上就是一個簡單的佇列使用,下面,我們來探討以下,如何在一個連結中使用多個佇列,這是laravel手冊最坑爹的地方,寫的不清不楚,我找了一段別人寫的,抄來大家自己琢磨就好:
這是一個必然需要考慮到的問題,我不可能將所有任務都放在一個叫default的佇列中,這樣不容易對佇列進行管理。要指定不同的佇列,非常簡單,在dispatch()後緊接著跟上onQueue()方法即可:
Demo::dispatch()->onQueue('emails');
不對啊,我好像沒有定義過這個叫 emails 的 queue。嗯,自然需要做出一點改動,在queue.php配置檔案中的redis配置queue從default改為{default},這樣做的效果就是佇列的名稱可以從執行的時候動態拿到,而不是寫死的。
如果使用 Lumen 框架,那麼直接這麼寫會報錯:Call to a member function onQueue() on string。原因在於 Lumen 的 Job 基類中並沒有使用Illuminate\Foundation\Bus\Dispatchable這個trait,而是直接使用Illuminate\Bus\Queueable中的onQueue()方法。
那麼現在就很清楚了,我們的 Job 類使用了Illuminate\Bus\Queueable這個trait,所以需要在 Job 類上呼叫這個onQueue()方法。
$job =newXXXJob();dispatch($job->onQueue('queue-name'));
當我們在開啟佇列的時候:
php artisan queue:work --queue=emails
這裡指定的佇列名 emails 和dispatch時指定的佇列名保持一致即可。
好了,這兩個關卡打通了,手冊後面的東西就沒什麼難的了,自個兒試一試吧?