環境構築からWEBアプリ開発・スマホアプリ開発まで。ときには動画制作やゲームも。

supilog
すぴろぐ

【初心者向け】Laravelでキューを使う

キューとは、待ち行列とも言われて、基本的なデータ構造の一つです。どんなデータ構造かと言うと、要素を投入された順番に並べておいて、先に投入されたデータから順に取り出して使うというような構造です。

電車を待っている人がどんどん溜まっていって、電車が来ると先頭に並んだ人から電車に乗り込んでいく。この電車待ちをしている人の状態がキューであり、先頭から処理されていくというのは、こういうイメージである。

実際には「非同期」と呼ばれる処理で使われる。リアリタイムに処理を行わず、処理を準備待ちの状態でためておいて、処理のパフォーマンスの中で1件ずつ処理をいていく。

例えばお買い物ができるECサイトお買い物完了処理は、「注文情報INSERT」「商品在庫情報UPDATE」「お買い物完了メール送信」など一般的に処理がやや多めである。この時に、これをリアルタイムでこなしていると、急激なアクセスにより処理が追いつかずにサーバーがダウンしてしまうような事がある。そういったことを防ぐために、注文をリアルタイムには処理せずに情報をキューに蓄積して、後ほど実処理を行うというようなイメージが分かりやすい。(※キューの説明なので、在庫の更新はどうするんだ?などは、ここでは無視)

ということで、Laravelでキューを扱ってみたいと思う。

環境

  • Laravel 6.20.0
  • PHP 7.4.13

キュー

事前準備

Laravelのデータベース設定をしておく。プロジェクト用のDBを用意し、接続ユーザーを作成する。それらが準備できたら、接続情報を.envファイルに記述しておく。(以下の箇所です)

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=supilog
DB_USERNAME=supiloguser
DB_PASSWORD=password

キュードライバの選択

キューを使うには、キューを保管しておく場所が必要になる。データベース、Beanstalkd、Amazon SQS、Redisが使えるようですが、一番シンプルなデータベースを選択してみる。

# マイグレーションファイルの作成
php artisan queue:table

# データベースをマイグレート
php artisan migrate

以上で、データベースにキューを使うためのテーブルが作成される。準備が出来たので、設定も変更しておく。デフォルトではsync設定になっているので以下のように変更。

QUEUE_CONNECTION=database

ビルトインサーバーなどでテストしている場合には、ここで再起動しておこう。

ジョブの作成

実際にキューに投入するデータは、ジョブと呼ばれるクラスです。以下のコマンドでジョブを生成すると、app/Jobs配下にファイルが作成されるはずだ。

php artisan make:job SendThanksMail

今回はお買い物完了メールを送信するようなジョブを想定して、以下のような簡単な処理にしてみる。内容はただメールを送信しているだけだ。重要な点はジョブ毎に可変な値をジョブクラスの変数として定義して、コンストラクタにてセットすること。

今回で言えば、送信先のメールアドレスをコンストラクタで設定する仕様になっている。実際にこのジョブがキューに投入される際に、メールアドレスも保管された状態で蓄積されているイメージだ。

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendThanksMail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $email;

    public function __construct($email)
    {
        $this->email = $email;
    }

    public function handle()
    {
        // 本来はここにメール送信の処理を記載すれば良いが、今回は処理を省略してログ出力するという処理に変更してテスト
        Log::info('メール送信 : ' . $this->email);
    }
}

テスト用のコントローラを準備

実際にECサイトがあった場合、お買い物完了処理の際に、コントローラ内でジョブの登録(dispatch)を行うので、今回はテスト用のコントローラを用意した。

php artisan make:controller SampleController
<?php

namespace App\Http\Controllers;

use App\Jobs\SendThanksMail;
use Illuminate\Http\Request;

class SampleController extends Controller
{
    public function index() {
        // ジョブを登録
        $email = 'supilog@example.com';
        SendThanksMail::dispatch($email);

        // 仮のビュー表示
        return view('welcome');
    }
}
<?php

Route::get('/', 'SampleController@index')->name('index');

コントローラ内で簡易的にジョブ登録を行っており、手動でメールアドレスを書き換えてテストをする。

動作確認

ここで、/にアクセスした際に、jobsテーブルにレコードが生成されれば、動作は成功である。

ワーカーの動作確認

キューに蓄積されたジョブは勝手には処理されない。ではどうやって処理されるのかというと、別途ワーカーを動作させておく必要がある。本番環境にて動作させる際には、常にワーカーのプロセスが動作していて、ジョブが登録されるとワーカーが逐次検知して動作をしてくれるという仕組みだ。

今回は手動で起動してみる。

まずは5種類のメールアドレスを使って、5個のジョブを投入した。こんな感じだ。

ワーカーを起動してみる。

php artisan queue:work

ログを見てみると・・

ジョブは実行され、jobsテーブルからレコードが消える。ログを見てみると↑のように、それぞれ別のメールアドレスでジョブが実行されていることが分かる。無事にキューの実行を試すことが出来た。

さいごに

本番環境にリリースする際の注意点としては、ワーカーを永続的に起動しておくために、supervisordなどを利用することになる。サーバーを再起動した際にも永続的に起動しておいてもらわねばならないからだ。

またサーバー移設をする際には、よくワーカーの起動を忘れがちになるので、注意!

それでは。