Help us understand the problem. What is going on with this article?

laravel-async-queueでLaravelの非同期キューを小さく使い始める

Laravelでキューを使うにはキューワーカーを立てる必要がありますが、キューワーカーのプロセス監視方法や、キューワーカーを使用しているアプリケーションのデプロイ方法を考える必要があり、結構面倒臭いです。
キューワーカーが不要な同期キュードライバもありますが、これだと非同期処理の旨味がありません。

laravel-async-queue

https://github.com/barryvdh/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にサービスプロバイダを追加します。

config/app.php
Barryvdh\Queue\AsyncServiceProvider::class,

config/queue.phpdefaultオプションをasyncに変更します。

config/queue.php
'default' => 'async', 

さらにconnectionsオプションに非同期キュードライバ用の設定を追加します。

config/queue.php
'async' => [
    'driver' => 'async',
    'table' => 'jobs',
    'queue' => 'default',
    'expire' => 60,
],

動作テスト

動作テストをしてみます。

テスト用のコントローラとイベントリスナーとイベントを用意します。

app/Http/Controllers/TestController.php
<?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');
    }
}
app/Listeners/TestListener.php
<?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')
    }
}
app/Events/TestEvent
<?php

namespace App\Events;

class TestEvent
{
}
app/Providers/EventServiceProvider.php
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テーブルに入れる処理をジョブ失敗イベントのコールバックとして書くことにします。
これはAppServiceProviderbootメソッドに書くのがいいと思います。

app/Providers/AppServiceProvider
\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>
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした