Edited at

Laravelのキューを使ってみた


キューとは

一部の処理を非同期的に実行ができます。

例えば、「ユーザー登録をしたときに完了メールを送る」処理を作るとします。

登録完了からメール送信完了して画面を表示するとメール送信処理がボトルネックになる可能性があります。

そこでメール送信処理をキューに変更し非同期にすることで、

登録完了後、メール送信処理を待たずに完了画面を表示することができます。


利用ケース

バッチで大量のデータを取得してDBへ保存。

またデータに対応する画像を取得してCloudinaryへアップする処理。

同期的に画像を取得してアップするとボトルネックになるので、

キューを使ってスピードアップを図る。

Laravel5.7とLaradockを利用しています。


Redis構築

今回はRedisを利用してやってみます。


laradock

$ docker-compose up -d redis


接続確認


laradock

$ docker-compose exec redis redis-cli

127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>


繋がりました!


Laravel設定


依存パッケージインストール


laradock

$ composer require predis/predis



.env

.envファイルを修正します。

# laradockで構築したときは以下のHOSTになる

REDIS_HOST=redis

# redis利用に変更
QUEUE_CONNECTION=redis


ジョブを作成する

ジョブ登録時に画像URLと画像名を受け取って、

実行時にローカルへ画像を保存する処理を書いてみます。


laradock

$ php artisan make:job ProductImage



ProductImage

<?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 ProductImage implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

private $url;

private $fileName;

/**
* Create a new job instance.
*
* @param string $url
* @param string $fileName
*/

public function __construct(string $url, string $fileName)
{
// 画像URL
$this->url = $url;

// 画像名
$this->fileName = $fileName;
}

/**
* Execute the job.
*
* @return void
*/

public function handle()
{
// storage直下へファイルを保存
file_put_contents("./storage/$this->fileName", file_get_contents($this->url));
Log::info('キュー実行完了');
}
}



コマンドの作成

実行用のコマンドを作ってみます。

Qiitaのロゴをダウンロードして保存する処理にします。

$ php artisan make:command FetchProductImage

$signatureの変更を忘れないようにしてください。


FetchProductImage

<?php

namespace App\Console\Commands;

use App\Jobs\ProductImage;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class FetchProductImage extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/

protected $signature = 'command:fetch_product_image';

/**
* The console command description.
*
* @var string
*/

protected $description = 'Command description';

/**
* Create a new command instance.
*
* @return void
*/

public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return mixed
*/

public function handle()
{
Log::info('コマンド実行開始');

// 非同期実行を明確化するために1分遅延させる
ProductImage::dispatch('https://cdn.qiita.com/assets/qiita-rectangle-71910ff07b744f263e4a2657e2ffd123.png', 'Qiita.png')
->delay(now()->addMinutes(1));

Log::info('コマンド実行完了');
}
}



実行


ワーカーの立ち上げ

キューを監視して実行してくれるワーカーを立ち上げます。

$ php artisan queue:work


コマンド実行

$ php artisan command:fetch_product_image


実行ログ確認

まだコマンドのログのみ表示されています。


log

[2018-10-27 16:09:26] local.INFO: コマンド実行開始

[2018-10-27 16:09:26] local.INFO: コマンド実行完了


Redis確認

キューが登録されているのを確認できます。

$ docker-compose exec redis redis-cli

127.0.0.1:6379> keys *
1) "queues:default:delayed"
127.0.0.1:6379> zrange queues:default:delayed 0 -1
1) "{\"displayName\":\"App\\\\Jobs\\\\ProductImage\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"timeout\":null,\"timeoutAt\":null,\"data\":{\"commandName\":\"App\\\\Jobs\\\\ProductImage\",\"command\":\"O:21:\\\"App\\\\Jobs\\\\ProductImage\\\":9:{s:26:\\\"\\u0000App\\\\Jobs\\\\ProductImage\\u0000url\\\";s:81:\\\"https:\\/\\/cdn.qiita.com\\/assets\\/qiita-rectangle-71910ff07b744f263e4a2657e2ffd123.png\\\";s:31:\\\"\\u0000App\\\\Jobs\\\\ProductImage\\u0000fileName\\\";s:9:\\\"Qiita.png\\\";s:6:\\\"\\u0000*\\u0000job\\\";N;s:10:\\\"connection\\\";N;s:5:\\\"queue\\\";N;s:15:\\\"chainConnection\\\";N;s:10:\\\"chainQueue\\\";N;s:5:\\\"delay\\\";O:25:\\\"Illuminate\\\\Support\\\\Carbon\\\":3:{s:4:\\\"date\\\";s:26:\\\"2018-10-27 16:10:26.434169\\\";s:13:\\\"timezone_type\\\";i:3;s:8:\\\"timezone\\\";s:10:\\\"Asia\\/Tokyo\\\";}s:7:\\\"chained\\\";a:0:{}}\"},\"id\":\"S9aNrtHKPtoNUFSfs9psLVkv39FnEpS1\",\"attempts\":0}"


一分後

ちょうど一分後にログが出ました。


log

[2018-10-27 16:09:26] local.INFO: コマンド実行開始

[2018-10-27 16:09:26] local.INFO: コマンド実行完了
[2018-10-27 16:10:26] local.INFO: キュー実行完了

キューワーカーでも実行が確認できます。


worker

[2018-10-27 16:10:26][S9aNrtHKPtoNUFSfs9psLVkv39FnEpS1] Processing: App\Jobs\ProductImage

[2018-10-27 16:10:26][S9aNrtHKPtoNUFSfs9psLVkv39FnEpS1] Processed: App\Jobs\ProductImage

しっかり画像が保存されています!

スクリーンショット 2018-10-27 16.13.56.png


複数のキューを実行

以下のように書き換えます。

今度はLaravelとDockerのロゴを保存する処理です。


FetchProductImage.php

        ProductImage::dispatch('https://www.docker.com/sites/default/files/vertical.png', 'docker.png')

->delay(now()->addMinutes(1));

ProductImage::dispatch('http://laravel.jp/assets/img/logo-head.png', 'laravel.png')
->delay(now()->addMinutes(1));



コマンド実行

$ php artisan command:fetch_product_image


Redis確認

2つ登録されているのが確認できます。


redis

127.0.0.1:6379> zrange queues:default:delayed 0 -1

1) "{\"displayName\":\"App\\\\Jobs\\\\ProductImage\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"timeout\":null,\"timeoutAt\":null,\"data\":{\"commandName\":\"App\\\\Jobs\\\\ProductImage\",\"command\":\"O:21:\\\"App\\\\Jobs\\\\ProductImage\\\":9:{s:26:\\\"\\u0000App\\\\Jobs\\\\ProductImage\\u0000url\\\";s:42:\\\"http:\\/\\/laravel.jp\\/assets\\/img\\/logo-head.png\\\";s:31:\\\"\\u0000App\\\\Jobs\\\\ProductImage\\u0000fileName\\\";s:11:\\\"laravel.png\\\";s:6:\\\"\\u0000*\\u0000job\\\";N;s:10:\\\"connection\\\";N;s:5:\\\"queue\\\";N;s:15:\\\"chainConnection\\\";N;s:10:\\\"chainQueue\\\";N;s:5:\\\"delay\\\";O:25:\\\"Illuminate\\\\Support\\\\Carbon\\\":3:{s:4:\\\"date\\\";s:26:\\\"2018-10-27 16:19:54.732950\\\";s:13:\\\"timezone_type\\\";i:3;s:8:\\\"timezone\\\";s:10:\\\"Asia\\/Tokyo\\\";}s:7:\\\"chained\\\";a:0:{}}\"},\"id\":\"WcQ4hgjZqqeAsTDbSTqweAvH1nisBwUz\",\"attempts\":0}"
2) "{\"displayName\":\"App\\\\Jobs\\\\ProductImage\",\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"maxTries\":null,\"timeout\":null,\"timeoutAt\":null,\"data\":{\"commandName\":\"App\\\\Jobs\\\\ProductImage\",\"command\":\"O:21:\\\"App\\\\Jobs\\\\ProductImage\\\":9:{s:26:\\\"\\u0000App\\\\Jobs\\\\ProductImage\\u0000url\\\";s:55:\\\"https:\\/\\/www.docker.com\\/sites\\/default\\/files\\/vertical.png\\\";s:31:\\\"\\u0000App\\\\Jobs\\\\ProductImage\\u0000fileName\\\";s:10:\\\"docker.png\\\";s:6:\\\"\\u0000*\\u0000job\\\";N;s:10:\\\"connection\\\";N;s:5:\\\"queue\\\";N;s:15:\\\"chainConnection\\\";N;s:10:\\\"chainQueue\\\";N;s:5:\\\"delay\\\";O:25:\\\"Illuminate\\\\Support\\\\Carbon\\\":3:{s:4:\\\"date\\\";s:26:\\\"2018-10-27 16:19:54.704917\\\";s:13:\\\"timezone_type\\\";i:3;s:8:\\\"timezone\\\";s:10:\\\"Asia\\/Tokyo\\\";}s:7:\\\"chained\\\";a:0:{}}\"},\"id\":\"YubmuatEQ4M3FRfKcB6CZzs0d8yI4Xlo\",\"attempts\":0}"


一分後

2つ実行されました!


worker

[2018-10-27 16:19:56][WcQ4hgjZqqeAsTDbSTqweAvH1nisBwUz] Processing: App\Jobs\ProductImage

[2018-10-27 16:19:57][WcQ4hgjZqqeAsTDbSTqweAvH1nisBwUz] Processed: App\Jobs\ProductImage
[2018-10-27 16:19:57][YubmuatEQ4M3FRfKcB6CZzs0d8yI4Xlo] Processing: App\Jobs\ProductImage
[2018-10-27 16:19:57][YubmuatEQ4M3FRfKcB6CZzs0d8yI4Xlo] Processed: App\Jobs\ProductImage

画像もしっかり保存されています。

スクリーンショット 2018-10-27 16.20.36.png


まとめ

簡単に非同期処理を実現できるので、いろいろなところで利用してみてはいかがでしょうか!