RitoLabo

laravel5.5のデータベースキュー投入とジョブ処理で非同期処理を実現する~劇的に高速化できるユーザーレスポンス~

  • 公開:
  • カテゴリ: PHP Laravel
  • タグ: Laravel,MySQL,artisan,migration,5.5,Events,Queues,dispatch,Job,worker

前回の記事でキュー投入とジョブ処理を実装しましたが、syncでのキューのため、実質非同期処理は行われていませんでした。

今回は、Laravel5.5にてデータベースを用いてキュー投入を行い、非同期のジョブ処理を実装していきます。

尚、キューとジョブの基本的な使い方については、以下を参考にしてください。
laravel5.5のキュー投入によるジョブ処理を導入する

今回のデモ環境は以下の通りです。

  • Linux CentOS 7
  • Apache 2.4
  • MySQL 5.7
  • PHP 7.1
  • Laravel 5.5

上記環境でなくてもArtisanコマンドが叩ける事、そしてMySQLを使えればOKです。それと、LaravelでのDB接続は終えておいてください。

アジェンダ
  1. キュー用のデータベースを作成
  2. キューサービスをデータベースへ切り替える
  3. ジョブクラスの作成と実装
  4. コントローラ・ビュー・ルーティング作成
  5. キューワーカを起動する
  6. 動作確認

キュー用のデータベースを作成

まずは、キューを投入するテーブルを作成します。

laravelルートディレクトリに移動し、以下のArtisanコマンドを叩いてマイグレーションファイルを作成します。

# laravelルートディレクトリへ移動
cd /path/to/laravel

# artisanコマンドでマイグレーションファイルを作成する
php artisan queue:table

# 実行結果
[demo@localhost laravel]# php artisan queue:table
Migration created successfully!

laravel/database/migrationsに、マイグレーションファイル「2017_11_19_112935_create_jobs_table.php」が生成されます。

laravel
├─ database
│ ├─ migrations
   │ └─ 2017_11_19_112935_create_jobs_table.php

生成されたマイグレーションファイルを見てみます。

laravel/database/migrations/2017_11_19_112935_create_jobs_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateJobsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('jobs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('jobs');
}
}
カラム構成[id][queue][payload][attempts][reserved_at][available_at][created_at]にて、jobsテーブルが作成される。という事のようです。

それではマイグレートしてjobsテーブルを作成します。以下のArtisanコマンドを叩きます。

# laravelルートディレクトリへ移動
cd /path/to/laravel

# artisanコマンドでマイグレートを行い、jobテーブルを作成する
php artisan migrate

# 実行結果
[demo@localhost laravel]# php artisan migrate
Migrating: 2017_11_19_112935_create_jobs_table
Migrated: 2017_11_19_112935_create_jobs_table

MySQLにログインして、テーブルが作成されているか確認します。

mysql> show tables;
+------------------+
| Tables_in_sample |
+------------------+
| jobs |
|
migrations |
+------------------+
5 rows in set (0.00 sec)
mysql> show columns from jobs;
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| queue | varchar(255) | NO | MUL | NULL | |
| payload | longtext | NO | | NULL | |
| attempts | tinyint(3) unsigned | NO | | NULL | |
| reserved_at | int(10) unsigned | YES | | NULL | |
| available_at | int(10) unsigned | NO | | NULL | |
| created_at | int(10) unsigned | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)

jobsテーブルの作成を確認できました。

キューサービスをデータベースへ切り替える

デフォルトではsyncになっていますので、laravel/.envを開いて、キューサービスをdatabaseへ切り替えます。

QUEUE_DRIVER=database

切り替えはこれで完了です。

ジョブクラスの作成と実装

次に、ジョブクラスを作成し、処理を実装していきます。artisanコマンドを叩いて、ジョブクラスを生成します。

# laravelルートディレクトリへ移動
cd /path/to/laravel

# artisanコマンドでジョブクラス生成する
php artisan make:job GenerateTextFile

# 実行結果
[demo@localhost laravel]# php artisan make:job GenerateTextFile
Job created successfully.

laravel/app/Jobs/GenerateTextFile.phpが生成されます。

laravel
├─ app
│ ├─ Jobs
│ │   ├─ GenerateTextFile.php

ファイルを開いて処理を実装します。

<?php

namespace App\Jobs;

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

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

/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// テキストファイル作成
$file = $this->makeTextFile();

// ループ回数を設定
$max = 300;
// 書き込み
$this->writeTextFileLoop($file, $max);
}

/**
* テキストファイルを作成する
* @return string
*/
private function makeTextFile()
{
$file = sprintf('%s/test.txt', storage_path('texts'));
if(file_exists($file)) unlink($file);
touch($file);
return $file;
}

/**
* テキストファイルに指定回数分の追加書き込みを行う
* @param $file
* @param $max
*/
private function writeTextFileLoop($file, $max)
{
for($i=0; $i<$max; $i++ ) {
$current = file_get_contents($file);
$current .= $i;
file_put_contents($file, $current);
}
}
}

テキストファイルを作成するmakeTextFile()メソッドと、テキストファイルに指定回数分の追加書き込みを行うwriteTextFileLoop()メソッドを定義し、handle()メソッドの中で実行しています。ループ回数は300回です。

コントローラ・ビュー・ルーティング作成

今回の検証用のコントローラとビューを作成して、ルーティングを行います。検証用という事で、キュー投入処理版と通常処理版の2つを書いていきます。

ルーティング
laravel/routes/web.php
Route::get('sample/queues/none', 'SampleController@queues_none');
Route::get('sample/queues/database', 'SampleController@queues_database');
http://YOURDOMAIN/sample/queues/none → 通常の同期処理を行う
http://YOURDOMAIN/sample/queues/database → 非同期処理を行う
コントローラ
laravel/app/Http/Controllers/SampleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Jobs\GenerateTextFile;

class SampleController extends Controller
{
public function queues_none()
{
$start = time();

$file = $this->makeTextFile();

$max = 300;
$this->writeTextFileLoop($file, $max);

return view('sample_queues', ['start' => $start]);
}

public function queues_database()
{
$start = time();

$this->dispatch(new GenerateTextFile());

return view('sample_queues', ['start' => $start]);
}

/**
* テキストファイルを作成する
* @return string
*/
private function makeTextFile()
{
$file = sprintf('%s/test.txt', storage_path('texts'));
if(file_exists($file)) unlink($file);
touch($file);
return $file;
}

/**
* テキストファイルに指定回数分の追加書き込みを行う
* @param $file
* @param $max
*/
private function writeTextFileLoop($file, $max)
{
for($i=0; $i<$max; $i++ ) {
$current = file_get_contents($file);
$current .= $i;
file_put_contents($file, $current);
}
}
}
queues_none()メソッドは通常処理(同期処理)用、queues_database()メソッドは非同期処理用になります。 時間計測のために処理開始時のUnixタイムスタンプをビューへ渡しています。
ビュー
laravel/resources/views/sample_queues.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Sample Queues</title>
</head>
<body>
<p>
Queues test!!
</p>
<?php
if(!empty($start)) { echo time() - $start; }
?>
</body>
</html>
現在のタイムスタンプから、コントローラから受け取ったタイムスタンプの差分を出す事で、処理にかかった時間(秒数)を出力しています。

キューワーカを起動する

動作確認を行う前に、キューワーカーを起動します。これを行わないと、データベースキューに投入されたジョブが処理されません。

# laravelルートディレクトリへ移動
cd /path/to/laravel

# artisanコマンドでキューワーカーを起動する
php artisan queue:work

コンソールが待機状態になれば、ワーカーが起動したことになります。

動作確認

キューワーカーを起動させたら、ブラウザからアクセスして動作確認を行いましょう。

  • 通常処理(同期処理) → http://YOURDOMAIN/sample/queues/none
  • 非同期処理 → http://YOURDOMAIN/sample/queues/database

として、それぞれブラウザからアクセスしてみます。

通常処理(同期処理)
通常処理(同期処理)
非同期処理
非同期処理

300回のループ処理ですが、通常処理の場合は表示までに16秒かかっているのに対し、非同期処理の場合は処理自体をキューに投げて別処理になっているので、表示まではわずか1秒と非常に高速です。

キューワーカーを起動したコンソール画面を確認してみましょう。

# 実行結果
[demo@localhost laravel]# php artisan queue:work
[2017-11-19 13:27:48] Processing: App\Jobs\GenerateTextFile
[2017-11-19 13:28:06] Processed: App\Jobs\GenerateTextFile

実行結果が出力されています。

まとめ

以上でデータベースを利用したキュー&ジョブ処理の作業は完了です。

処理をキューへ投下する事で処理が分離され、非常に高速にレスポンスを返せるようになります。

また、テーブルに挿入されたキュー情報は処理されたら削除されるので、データベースも圧迫されません。

そのほかにも、処理の優先順位付けや、処理遅延時間の設定など、色々行えるので、是非試してみてください。