Laravelでキューを使うにはキューワーカーを立てる必要がありますが、キューワーカーのプロセス監視方法や、キューワーカーを使用しているアプリケーションのデプロイ方法を考える必要があり、結構面倒臭いです。
キューワーカーが不要な同期キュードライバもありますが、これだと非同期処理の旨味がありません。
laravel-async-queue
laravel-async-queueというパッケージが非同期キュードライバを提供しています。
この非同期キュードライバを使うと、ジョブをバックグラウンドプロセスで即時に実行できるようになり、キューワーカーを立てることなく非同期処理の旨味を得ることができます。
インストール方法
なお、この記事では下記のバージョンのソフトウェアを使用しています。
ソフトウェア | バージョン |
---|---|
PHP | 7.2.12 |
Laravel | 5.5.44 |
laravel-async-queue | v0.7.3 |
laravel-async-queueをComposerでインストールします。
composer require barryvdh/laravel-async-queue
config/app.php
にサービスプロバイダを追加します。
Barryvdh\Queue\AsyncServiceProvider::class,
config/queue.php
のdefault
オプションをasync
に変更します。
'default' => 'async',
さらにconnections
オプションに非同期キュードライバ用の設定を追加します。
'async' => [
'driver' => 'async',
'table' => 'jobs',
'queue' => 'default',
'expire' => 60,
],
動作テスト
動作テストをしてみます。
テスト用のコントローラとイベントリスナーとイベントを用意します。
<?php
namespace App\Http\Controllers;
use App\Events\TestEvent;
use App\Http\Controllers\Controller;
use Log;
class TestController extends Controller
{
public function test()
{
Log::debug('1');
event(new TestEvent());
Log::debug('3');
}
}
<?php
namespace App\Listeners;
use App\Events\TestEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Log;
class TestListener implements ShouldQueue
{
public function handle(TestEvent $event)
{
Log::debug('2')
}
}
<?php
namespace App\Events;
class TestEvent
{
}
protected $listen = [
'App\Events\TestEvent' => [
'App\Listeners\TestListener',
],
];
コントローラのアクションを実行すると、下記のようなログが出力されます。
[2020-02-13 23:36:45] local.APP.DEBUG: 1 [] {"uid":"93eb38d","process_id":1017}
[2020-02-13 23:36:45] local.APP.DEBUG: 3 [] {"uid":"93eb38d","process_id":1017}
[2020-02-13 23:36:47] local.APP.DEBUG: 2 [] {"uid":"535fd9a","process_id":1046}
1→3→2となっており、非同期処理されていることがわかります。
プロセスIDとUIDもコントローラとイベントリスナーで別になっています。
ちなみに同期ドライバの場合です。
[2020-02-13 23:38:22] local.APP.DEBUG: 1 [] {"uid":"a9173e4","process_id":1020}
[2020-02-13 23:38:22] local.APP.DEBUG: 2 [] {"uid":"a9173e4","process_id":1020}
[2020-02-13 23:38:22] local.APP.DEBUG: 3 [] {"uid":"a9173e4","process_id":1020}
1→2→3となっており、同期処理です。
もちろんプロセスIDとUIDもコントローラとイベントリスナーで同じです。
失敗ジョブを扱えるようにする
非同期キュードライバは、キューワーカーを使っていないため、ジョブが1回失敗すると自動ではリトライされません。
また、ジョブ失敗ベントが発行されず、失敗したジョブはjobs
テーブルに残ったままで、failed_jobs
テーブルに入りません。
これだと運用がつらいので、ジョブ失敗をSlackに通知したりできるように、ジョブ失敗イベントが発行されるようにします。
イベントリスナーの$tires
プロパティを1
に設定すると、ジョブ失敗ベントが発行されるようになります1。
class TestListener implements ShouldQueue
{
public $tires = 1;
...
}
トレイトにしておくと便利かもしれません。
trait AsyncQueueable
{
public $tires = 1;
}
class TestListener implements ShouldQueue
{
use AsyncQueueable;
...
}
これでジョブ失敗イベントが発生するようになります。
同時に、失敗ジョブがjobs
テーブルから消えるようになります。
ただ、このままだと失敗ジョブがjobs
テーブルにもfailed_jobs
テーブルにも残らなくなってしまうので、失敗ジョブをfailed_jobs
テーブルに入れる処理をジョブ失敗イベントのコールバックとして書くことにします。
これはAppServiceProvider
のboot
メソッドに書くのがいいと思います。
\Illuminate\Support\Facades\Queue::failing(function (\Illuminate\Queue\Events\JobFailed $event) {
if ($event->job->getConnectionName() == 'async') {
// 失敗ジョブをfaled_jobsテーブルに保存する
$id = $this->app['queue.failer']->log(
$event->connectionName,
$event->job->getQueue(),
$event->job->getRawBody(),
$event->exception
);
}
// Slackへの通知など
...
});
ちなみに、log
メソッドはfailed_jobs
テーブルのIDが返ってきます。
Slackに通知する時に使うと、後でジョブを手動でリトライする際に便利でいいと思います。
これで失敗ジョブがfailed_jobs
テーブルに入るようになります。
リトライが必要な場合は、下記のArtisanコマンドを実行します。
artisan queue:retry <failed_jobsテーブルのID>
これでfailed_jobs
テーブルの失敗ジョブがjobs
テーブルに戻ります。
さらに下記のArtisanコマンドを実行するとジョブを実行できます。
artisan queue:async <jobsテーブルのID>