キューとは
一部の処理を非同期的に実行ができます。
例えば、「ユーザー登録をしたときに完了メールを送る」処理を作るとします。
登録完了からメール送信完了して画面を表示するとメール送信処理がボトルネックになる可能性があります。
そこでメール送信処理をキューに変更し非同期にすることで、
登録完了後、メール送信処理を待たずに完了画面を表示することができます。
利用ケース
バッチで大量のデータを取得してDBへ保存。
またデータに対応する画像を取得してCloudinaryへアップする処理。
同期的に画像を取得してアップするとボトルネックになるので、
キューを使ってスピードアップを図る。
Laravel5.7とLaradockを利用しています。
Redis構築
今回はRedisを利用してやってみます。
$ docker-compose up -d redis
接続確認
$ docker-compose exec redis redis-cli
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379>
繋がりました!
Laravel設定
依存パッケージインストール
$ composer require predis/predis
.env
.env
ファイルを修正します。
# laradockで構築したときは以下のHOSTになる
REDIS_HOST=redis
# redis利用に変更
QUEUE_CONNECTION=redis
ジョブを作成する
ジョブ登録時に画像URLと画像名を受け取って、
実行時にローカルへ画像を保存する処理を書いてみます。
$ php artisan make:job 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
の変更を忘れないようにしてください。
<?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
実行ログ確認
まだコマンドのログのみ表示されています。
[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}"
一分後
ちょうど一分後にログが出ました。
[2018-10-27 16:09:26] local.INFO: コマンド実行開始
[2018-10-27 16:09:26] local.INFO: コマンド実行完了
[2018-10-27 16:10:26] local.INFO: キュー実行完了
キューワーカーでも実行が確認できます。
[2018-10-27 16:10:26][S9aNrtHKPtoNUFSfs9psLVkv39FnEpS1] Processing: App\Jobs\ProductImage
[2018-10-27 16:10:26][S9aNrtHKPtoNUFSfs9psLVkv39FnEpS1] Processed: App\Jobs\ProductImage
しっかり画像が保存されています!
複数のキューを実行
以下のように書き換えます。
今度はLaravelとDockerのロゴを保存する処理です。
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つ登録されているのが確認できます。
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つ実行されました!
[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
画像もしっかり保存されています。
まとめ
簡単に非同期処理を実現できるので、いろいろなところで利用してみてはいかがでしょうか!