場景:人數增多,後臺直接循環推送消息卡死,存儲到 redis 隊列中執行
基礎操作:查看隊列信息
例:keys * -> "queues:WalletReportJob" (獲取 key)
type queues:WalletReportJob (獲取類型) -> list
lrange queues:WalletReportJob 0 12 -> 查詢列表 12 條數據
正文:[原文鏈接](https://segmentfault.com/a/1190000015097364)
我們仍然從配置文件開始,首先我們需要在配置文件中配置默認隊列驅動爲Redis。lumen沒有配置文件,可以從laravel項目中拷貝一份config目錄過來。
隊列配置文件是config/queue.php
:
return [
'default' => env('QUEUE_DRIVER', 'sync'),
'connections' => [
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'expire' => 60,
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'default',
'expire' => 60,
],
],
'failed' => [
'database' => 'mysql', 'table' => 'failed_jobs',
],
];
配置文件第一個配置項default
用於指定默認的隊列驅動,修改.env中的QUEUE_DRIVER
即可。
connections
配置項包含了Laravel支持的所有隊列驅動,我們使用Redis驅動,所以需要配置redis項:connection對應config/database.php中redis的default配置
;queue
爲默認隊列名稱;expire
爲隊列任務過期時間(秒)。這裏我們可以保持其默認配置不變。
failed
配置項用於配置失敗隊列任務存放的數據庫及數據表。這裏我們需要按照自己的數據庫配置對其做相應修改。
要使用 redis 隊列驅動,需要在配置文件 config/database.php
中配置 Redis
數據庫連接。
如果 Redis 隊列連接使用 Redis Cluster(集羣),隊列名稱必須包含 key hash tag
,以確保給定隊列對應的所有 Redis keys 都存放到同一個 hash slot
:
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => '{default}',
'retry_after' => 90,
],
注:對一般中小型應用推薦使用 Redis
作爲隊列驅動。
三、驅動預備知識
數據庫
要使用 database
隊列驅動,你需要數據表保存任務信息(比如失敗任務)。要生成創建這些表的遷移,可以在項目目錄下運行 Artisan
命令 queue:table
,遷移被創建之後,可以使用 migrate
命令生成這些表:
php artisan queue:table
php artisan queue:failed_jobs
php artisan migrate
運行後生成failed_jobs
、jobs
、migrations
三張表。
四、創建任務
1、生成任務類
通常,所有的任務類都保存在 app/Jobs
目錄。laravel
中 app/Jobs
不存在,在運行 Artisan
命令 make:job
的時候,它將會自動創建。你可以通過 Artisan CLI 來生成隊列任務類:
php artisan make:job ProcessPodcast
生成的類都實現了 Illuminate\Contracts\Queue\ShouldQueue
接口, 告訴 Laravel 將該任務推送到隊列,而不是立即運行:
lumen
中 app/Jobs
目錄已經存在,由於不能執行artisan命令,直接複製目錄中的ExampleJob.php
即可。該文件繼承Job.php
從而實現了ShouldQueue
。
2、任務類結構
任務類非常簡單,通常只包含處理該任務的 handle
方法,在任務被處理的時候調用,注意我們可以在任務的 handle 方法中進行依賴注入
。Laravel 服務容器會自動注入這些依賴。
3、分發任務
創建好任務類後,就可以通過任務自身的 dispatch
方法將其分發到隊列。dispatch
方法需要的唯一參數就是該任務的實例:
PushWeChatJob::dispatch($product)
lumen中用法:
4、指定最大失敗次數
指定隊列任務最大失敗次數的一種實現方式是通過 Artisan 命令 --tries
切換:
php artisan queue:work --tries=3
不過,你還可以在任務類自身定義最大失敗次數來實現更加細粒度的控制,如果最大失敗次數在任務中指定,則其優先級高於命令行
指定的數值:
<?php
namespace App\Jobs;
class ProcessPodcast implements ShouldQueue
{
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 5;
}
5、超時
注:timeout
方法爲PHP7.1+
和pcntl
擴展做了優化。
類似的,隊列任務最大運行時長(秒)可以通過 Artisan 命令上的 --timeout
開關來指定:
php artisan queue:work --timeout=30
同樣,你也可以在任務類中定義該任務允許運行的最大時長(單位:秒),任務中指定的超時時間優先級也高於命令行定義的數值:
<?php
namespace App\Jobs;
class ProcessPodcast implements ShouldQueue
{
/**
* The number of seconds the job can run before timing out.
*
* @var int
*/
public $timeout = 120;
}
6、基於時間的嘗試次數
除了定義在任務失敗前的最大嘗試次數外,還可以定義在指定時間內允許任務的最大嘗試次數
,這可以通過在任務類中添加 retryUntil
方法來實現:
/**
* Determine the time at which the job should timeout.
*
* @return \DateTime
*/
public function retryUntil()
{
return now()->addSeconds(5);
}
注:還可以在隊列時間監聽器中定義 retryUntil
方法。
7、頻率限制
注:該功能要求應用可以與 Redis 服務器進行交互。
如果應用使用了 Redis
,那麼可以使用時間或併發
來控制隊列任務。該功能特性在隊列任務與有頻率限制的 API 交互時很有幫助,例如,通過 throttle
方法,你可以限定給定類型任務每 60 秒只運行 10 次。如果不能獲取鎖,需要將任務釋放回隊列以便可以再次執行:
Redis::throttle('key')->allow(10)->every(60)->then(function () {
// Job logic...
}, function () {
// Could not obtain lock...
return $this->release(10);
});
注:在上面的例子中,上面的方法可能無法找到,但是直接複製即可使用(具體還不清楚,知道的大神可以留言指教)。key 可以是任意可以唯一標識你想要限定訪問頻率的任務類型的字符串。舉個例子,這個鍵可以基於任務類名和操作 Eloquent 模型的 ID 進行構建。
8、最大進程數量
除此之外,還可以指定可以同時處理給定任務的最大進程數量。這個功能在隊列任務正在編輯一次只能由一個任務進行處理的資源時很有用。例如,使用 funnel 方法你可以給定類型任務一次只能由一個工作進程進行處理:
Redis::funnel('key')->limit(1)->then(function () {
// Job logic...
}, function () {
// Could not obtain lock...
return $this->release(10);
});
注:使用頻率限制時,任務在運行成功之前需要的最大嘗試次數很難權衡,因此,將頻率限制和基於時間的嘗試次數結合起來使用是個不錯的選擇。
9、運行隊列進程
Laravel 自帶了一個隊列進程用來處理被推送到隊列的新任務。你可以使用 queue:work
命令運行這個隊列進程。請注意,隊列進程開始運行後,會持續監聽隊列,直至你手動停止或關閉終端:
php artisan queue:work
注:爲了保持隊列進程queue:work
持續在後臺運行,需要使用進程守護程序,比如 Supervisor 來確保隊列進程持續運行。簡單處理可以使用
php artisan queue:work --daemon &
10、運行隊列監聽器
開始進行隊列監聽
laravel 包含了一個 Artisan 命令來運行推送到隊列中的任務的執行。你可以使用 queue:listen
命令來運行監聽器:
php artisan queue:listen
注意:queue:listen
要比queue:work --daemon
性能差很多。
你也可以指定監聽哪一個連接的隊列:
php artisan queue:listen connection-name
請記住,隊列進程是長生命週期的進程,會在啓動後駐留內存。若應用有任何改動將不會影響到已經啓動的進程。所以請在發佈程序後,重啓隊列進程
。
可以通過 Aritisan 命令 queue:restart
來優雅地重啓隊列進程:
php artisan queue:restart
該命令將在隊列進程完成正在進行的任務後,結束該進程,避免隊列任務的丟失或錯誤。由於隊列進程會在執行 queue:restart
命令後死掉,你仍然需要通過進程守護程序如 Supervisor
來自動重啓隊列進程。
注:隊列使用緩存來存儲重啓信號,所以在使用此功能前你需要驗證緩存驅動配置正確
補坑:
php artisan make:job PushWeChatJob (創建 job,PushWeChatJob 自己 job 的名字)
php artisan queue:work redis --queue=PushWeChatJob (監聽 job,注意:每次修改代碼都要重新運行監聽,大坑)
總結:隊列就像是銀行辦理業務,如果所有請求一次發送過去,銀行人手不夠就會阻塞,如果排隊,一個一個處理,就會沒問題了。監聽就類似窗口要一直有人員,這樣才能知道有沒有人在辦理業務,不然就算大家都在排隊,但是沒人來處理,也就一直堵在那裏。