【Laravelスキルアップ講座】Laravel JobとQueueも設定はこんなに簡単
LaravelではJob(ジョブ)もQueue(キュー)のどちらも使用することなくアプリケーションの構築を行うことができるので、設定したこともなければ存在さえしらない人が多いかもしれません。本文書では入門者でもわかるようにJobの作成方法からQueueの使い方まで簡単なコードを使って説明を行っていきます。バージョンによってコアな機能の違いはないため一度学習して理解することができれば長く活用できる知識になります。これまで使ったことがない人もぜひこの機会にLaravelのQueueにチェレンジしてください。
目次
用語の確認
Queue(キュー)、Job(ジョブ)と Worker(ワーカー) とは
キューは処理が実行されるのを待っているジョブのリストです。一度理解すればこれだけの文章でキューとジョブをイメージすることができますが初めての人であればキーとジョブをイメージするのは難しいと思うので身近なコンビニを例にして説明してみます。
コンビニでは昼時になるとオフィス街ではレジの前でたくさんの人がレジ待ちしていると思います。このレジ待ちをしている列がキューを意味し、レジで行うお客様の会計処理1つ1つがジョブになります。もっともよく使われる例が銀行で銀行に並んでいる人の列がキューで銀行の窓口での処理がジョブになります。高速の料金所などもキューとジョブで世の中のあらゆるところにキューとジョブは存在しています。
キューとジョブ以外にLaravelでは後ほどもう一つ重要なWorker(ワーカー)という単語がでてきますが、WorkerはJobを処理するプロセスでレジであればレジ会計処理(ジョブ)を行う店員がワーカーに対応します。
同期と非同期
キューを理解するためには、さらに同期処理と非同期処理という言葉を理解しておく必要があります。最近頻繁に街中で見かけるUberEatsを例に考えてみましょう。
UberEatsではお客様がスマホを通して注文を行います。注文を受け取った店は料理の調理を開始します。それと同時にUberEatsからお店の近くの配達員に連絡が入り、配達を行いたい配達員が決定され、お店に料理を取りに行きます。その後配達員がお店で料理を受け取ってお客様の元に料理を運びます。
これが同期処理で行われていると店が注文を受け取ってもすぐにUberEatsの配達員には連絡は行われません。配達員への連絡はお店の料理ができあがるまで待つ必要があります。料理ができあがったらUberEatsから配達員に連絡が入り、配達員が決定した後配達員はお店に料理を取りに向かいします。お店は配達員が料理をお客様の元に届けるまで次の注文を受け取らず待ちます。配達員がお客様に届けた連絡を受けて次の注文を受けます。
通常は非同期に処理が行われているので、お店に注文が入ると料理が出来上がるのを待たず、配達員に連絡が入り、調理中に配達員がお店に向かいます。お店は料理ができたら、次の注文の料理を開始します。次の料理を調理している最中に先ほどできあがった料理を配達員に渡します。
同期と非同期はお客様、お店にとってどちらがいいでしょうか?非同期のほうが明らかに効率的です。実際の世の中ではこのように非同期処理が頻繁に行われています。プログラムでも非同期処理をうまく活用することで効率よく処理を行うことができます。
この例でのキューは店にとっては注文です。一人しか料理人がいない場合は順番にキューに入った処理を行なっていかなければなりません。ジョブは注文を受けたメニューになります。UberEatsの仕組みは細かくはわかりませんが、配達員の場合は、1件配送依頼の処理が完了したら、また次の1件の配送依頼を受けるのであればキューはなく同期処理を行なっていることになります。
Laravelのどこで利用する
キーをLaravelのどこで利用するのかという疑問が出てくるかと思います。本文書でも後ほど動作確認をするメール送信の処理でキューの効果の大きさを感じることができます。キーを設定するかどうかでメール送信を含む処理の時間に大きな違いができます。
環境構築
ジョブとキューの動作確認を行うためには、Laravel環境の構築を行う必要があります。環境構築については簡単に手順を説明していきます。
Laravelのインストールと設定
composerコマンドでLaravelのインストールを行います。プロジェクト名はjob_queueと設定しています。名前は任意いの名前を付けることができます。
$ composer create-project --prefer-dist laravel/laravel job_queue
データベースには簡易的に利用できるsqliteデータベースを利用します。touchコマンドでデータベースファイルを作成します。コマンドはLaravelのインストールディレクトリjob_queueの下で実行します。
$ cd job_queue
$ touch database/database.sqlite
.envファイルでデータベースの設定であるDB_CONNECTIONの値をデフォルトのmysqlからsqliteに変更します。
DB_CONNECTION=sqlite
ユーザ登録処理の中でキューの動作確認を行うため認証機能の追加を行います。Laravel8の場合はLaravel/Breezeを利用します。
Laravel8のBreezeを利用した場合はcomposerコマンドでlaravel/breezeのインストールが必要となります。
% composer require laravel/breeze --dev
% php artisan breeze:install
Breeze scaffolding installed successfully.
Please execute the "npm install && npm run dev" command to build your assets
$ npm install && npm run dev
Laravel7までのバージョンではphp artisan make:authを実行することで認証機能をインストールすることができます。
$ php artisan make:auth
Authentication scaffolding generated successfully.
php artisan migrateコマンドでテーブルの作成を行います。users, password_resetsテーブルが作成されます。Laravel8ではfailed_jobsテーブルも作成されます。failed_jobsはキューに入ったジョブが失敗した時に利用されるキューに関連したテーブルです。
% php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table (4.00ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table (3.32ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated: 2019_08_19_000000_create_failed_jobs_table (1.57ms)
メール送信のSMTP設定
メール送信を通してLaravelにおけるキューの効果を確認していきます。そのため動作確認用のメールの設定を行います。本番環境ではここで利用するmailtrapの情報ではなく各環境にあったメール設定を行います。
ここではダミーのSMTPサーバのmailtrapを利用します。.envファイルを開いてmailTrapの接続情報を設定します。
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=XXXXXXXXXXXXX //各環境による異なる
MAIL_PASSWORD= XXXXXXXXXXXXX // 各環境による異なる
MAIL_FROM_ADDRESS=from@example.com
MAIL_FROM_NAME=Example
php artisan serveで開発用WEBサーバを起動し、ユーザ登録画面が表示されるか確認を行なってください。今後はこの画面からユーザ登録を行い、キューを使用する場合としない場合の比較を行なっていきます。バージョンによってユーザの登録画面は異なりますがユーザ登録にName, Email, Passwordを入れることに違いはありません。
ここまでの作業でジョブ、キューの動作確認を行うためのLaravelの環境設定は完了です。
Queue(キュー)を使用しない場合
キューを使用せずユーザ登録後にWelocomeメールを送信するコードを記述します。
ユーザ登録の処理はApp¥Http¥Controllers¥Auth¥RegisterController.phpファイル内(Laravel8ではRegisteredUserController.php)のcreateメソッドで行われているため、createメソッドの中に登録後の処理を記述します。
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
// 上記まででユーザの登録が完了するのでメール送信処理をここに記述
return $user;
}
送信するメールの設定
送信するメールの内容を記述するためにMailableクラスのWelcomeMailを作成します。
$ php artisan make:mail WelcomeMail
Mail created successfully.
実行後、app¥Mailフォルダの下にWelcomeMail.phpが作成されます。
WelcomeMail.phpのbuildメソッドを下記のように書き換えます。viewメソッドで指定したemails.welcomeファイルにはメールの内容を記述します。subjectメソッドはメールのタイトルを入力します。
public function build()
{
return $this->view('emails.welcome')
->subject('This is welcome mail');
}
resource¥viewsフォルダの下にemails¥welcome.blade.phpファイルを作成します。
welcome.blade.phpファイルには以下を書き込みます。送信されるメールには下記の内容のみ記述されています。
ユーザ登録ありがとうございます。
メール送信の処理を追加
App¥Http¥Controllers¥Auth¥RegisterController.phpファイル内のcreateメソッドの中にMailファサードを利用してメール送信のコードを追加します。(Laravel8ではRegisteredUserController.php)
登録したユーザのメールアドレス宛にWelcomeメールを送信する処理です。
// 中略
use Mail;
use App\Mail\WelcomeMail;
// 中略
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
Mail::to($user->email)->send(new WelcomeMail());
}
待ちの長さは環境にも依存しますがユーザがユーザ登録画面ですべての情報を入力してRegisterボタンを押してから次のページに行くまでに3-4秒間ブラウザが応答していない状態になることを必ず確認してください。この待ちがキューと関係してきます。
ユーザの登録画面が完了するとメールが送信されます。mailtrapをしている場合はmailtrap上で送信されたメールの内容を確認することができます。mailtrapを利用していない場合は設定したメールアドレス宛てにメールが届いているか確認します。
Queue(キュー)を利用した場合
キューの設定ファイル
キューの設定は、.envファイルとconfig¥queue.phpファイルの中で行います。
キューの設定について
キューを利用していない場合、ユーザ登録のRegisterボタンを押してから登録後の次の画面に行くまでに数秒かかることを確認しました。これはメールを送信する処理が終わるまで次の処理が行えず待ちが生じているためです。
デフォルトではすべての処理が同期処理で行われるため、.envファイルのキューの設定を見ると同期のsynchronizeという意味を持つsyncが設定されています。
QUEUE_CONNECTION=sync
キューの設定ファイルであるconfig¥queue.phpファイルのconnectionsを見るとsync以外に”sync”, “database”, “beanstalkd”, “sqs”, “redis”などのキューが準備されています。
最初にもっとも設定が簡単なdatabaseをキューを利用します。そのため.envファイルのQUEUE_CONNECTIONの値をsyncからdatabaseに変更してください。
キューにデータベースを利用
.envファイルのQUEUE_CONNECTIONをsyncからdatabaseに変更します。キュー冒頭で説明したコンビニの例のようにレジの前に待ち行列があるようにキューにはジョブが貯まることになります。そのためキューはジョブをリスト化して保存しておく必要があります。。設定値をdatabaseにするとキューの保存場所がデータベース内のテーブルになります。
そのためデータベースにキューに入ってるジョブを保存するテーブルを作成する必要がありますがテーブル作成についての処理とテーブルの設計はLaravel側で事前準備されているので、php artisan queue:tableコマンドを実行します。
$ php artisan queue:table
Migration created successfully!
app¥database¥migrationsディレクトリにjobsテーブルのマイグレーションファイルが作成されます。php artisan migrateコマンドを実行するとjobsテーブルが作成されます。キューを使用するとこのjobsテーブルの中に未実行のジョブが保存されていきます。
$ php artisan migrate
Migrating: 2019_08_22_134429_create_jobs_table
Migrated: 2019_08_22_134429_create_jobs_table (0 seconds)
これでdatabaseキューを使う準備は完了です。
ジョブの作成
ジョブ単位でキューを利用するため先ほど作成したメール送信のコードをジョブとして登録します。ジョブの作成はphp artisan make:jobコマンドを使用します。
$ php artisan make:job SendWelcomeMail
Job created successfully.
ジョブはapp¥Jobsディレクトリの下に作成されます。作成されたSendWelcomeMail.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 SendWelcomeMail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
//
}
public function handle()
{
//
}
}
メール送信のコードをSendWelcomeMail.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;
use Mail;
use App\Mail\WelcomeMail;
use App\User;
use App\Models\User;// Laravel8の場合
class SendWelcomeMail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function handle()
{
Mail::to($this->user->email)->send(new WelcomeMail());
}
}
ジョブの作成は完了です。
キューを使用
ジョブの作成が完了したので、キューを使用します。ジョブをキューを使って実行したい場合はdispatchメソッドを使用します。
RegisterController.phpの処理にジョブSendWelcomeMailを追加します。
use App\Jobs\SendWelcomeMail;
// 中略
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
SendWelcomeMail::dispatch($user);
return $user;
}
ここまでの設定が完了したら再度ユーザの登録を行ってください。
ユーザ登録を登録画面から実行するとキューを使用する前は次の画面に進むまでに数秒待つ必要がありましたがキューを使用するとすぐに次の画面に移動します。しかし今の設定のままでは何分経過してもジョブの中で実行されているWelcomeメールが送信されず、MaiTrapのinBoxにもメールが届きません。
キューを保存しているjobsテーブルを確認してみましょう。
jobsテーブルの確認
GUIツールを(TablePlus)使ってjobsテーブルの中身を確認するとSendWelcomeMailのジョブが登録されていることがわかります。
TablePlusのようなGUIのツールが使えない場合はコマンドラインでもSQLiteに接続することができます。sqlite3コマンドでsqliteのデータベースファイルであるdatabase/database.sqliteファイルを指定しています。
データベースへの接続後はselect * from jobsコマンドを実施しています。
% sqlite3 database/database.sqlite
SQLite version 3.32.3 2020-06-18 14:16:19
Enter ".help" for usage hints.
sqlite> select * from jobs;
1|default|{"uuid":"b36a6ef1-7f64-4981-a6a8-ade4b007ae31","displayName":"App\\Jobs\\SendWelcomeMail","job":"Illuminate\\Queue\\CallQueuedHandler@call","maxTries":null,"maxExceptions":null,"backoff":null,"timeout":null,"retryUntil":null,"data":{"commandName":"App\\Jobs\\SendWelcomeMail","command":"O:24:\"App\\Jobs\\SendWelcomeMail\":11:{s:4:\"user\";O:45:\"Illuminate\\Contracts\\Database\\ModelIdentifier\":4:{s:5:\"class\";s:15:\"App\\Models\\User\";s:2:\"id\";i:1;s:9:\"relations\";a:0:{}s:10:\"connection\";s:6:\"sqlite\";}s:3:\"job\";N;s:10:\"connection\";N;s:5:\"queue\";N;s:15:\"chainConnection\";N;s:10:\"chainQueue\";N;s:19:\"chainCatchCallbacks\";N;s:5:\"delay\";N;s:11:\"afterCommit\";N;s:10:\"middleware\";a:0:{}s:7:\"chained\";a:0:{}}"}}|0||1619847185|1619847185
jobsテーブルにはキューに残っている未実行のジョブが保存されるのでSendWelcomeMailジョブが未実行のため、メールが送信されていないことがわかります。
ではジョブはどのタイミングで実行されるのでしょう?
キューにあるジョブの実行
キューの中に入っているジョブは自動で実行されるわけではなくジョブを実行する別のプロセスであるWorker(ワーカー)が必要となります。キューの中にあるジョブを実行するためには、ワーカーを起動する必要があります。ワーカーを起動するためにphp artisan queue:workコマンドを実行します。
$ php artisan queue:work
[2019-08-25 15:34:09][3] Processing: App\Jobs\SendWelcomeMail
[2019-08-25 15:34:12][3] Processed: App\Jobs\SendWelcomeMail
php artisan queue:work実行後、起動したワーカーによりキューに残っていたSendWelcomeMailの実行が開始(Processing)され、しばらくして処理が完了(Processed)します。完了するとMailtrapのinBoxにWelcomeメールが受信されていることを確認することができます。
php artisan queue:workコマンドを実行し、プロセスを終了しなければワーカーは起動したままの状態となります。再度別のユーザを追加してみましょう。
今度はワーカーが起動している状態なので、ユーザを登録するとすぐにWelcomeメールが送信されます。ワーカーが処理を行なったことも確認することができます。
$ php artisan queue:work
[2019-08-25 15:34:09][3] Processing: App\Jobs\SendWelcomeMail
[2019-08-25 15:34:12][3] Processed: App\Jobs\SendWelcomeMail
[2019-08-25 15:39:25][4] Processing: App\Jobs\SendWelcomeMail
[2019-08-25 15:39:28][4] Processed: App\Jobs\SendWelcomeMail
ここでは動作確認のためワーカーをphp artisan queue:workコマンドで起動しています。この場合何かの原因でワーカープロセルが停止したらキューに入ったジョブの処理は停止してしまします。そのためワーカプロセスを監視する必要があります。監視することでワーカープロセスが何かの原因で停止してもその停止を検知し再度起動することがかのうとなります。Linux環境ではワーカープロセスの状態はsupervisorで監視を行うことができます。Ubutun環境でのsupervisorの設定手順については下記の文書にまとめていますのでsupervisorの動作確認も合わせて行ってください。
Notification(通知)でキューを利用
キューを利用する場合にこれまではJobを利用して行ってきましたがNotificationでキューを利用することも可能です。NotificationはSMS, Slack, Emailを経由してユーザに通知を行いたい場合に利用する機能です。
Notificationはphp artisan make:notificationコマンドを利用して作成することができます。ここでは、SendWelcomeNotificationという名前で作成します。
% php artisan make:notification SendWelcomeNotification
Notification created successfully.
実行するとappの下にNotificationsというフォルダが作成されその中にSendWelcomeNotification.phpファイルが作成されます。
作成するとデフォルトでEmailでの通知コードが記述されているので何も変更しなくてもそのまま利用することができます。
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class SendWelcomeNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
ここではNotificationでキューを利用する方法についての動作確認なのでNotificationの中身のコードについては触れません。
ユーザ登録時にJobを実行させていましたがJobでメール送信していた箇所をNotificationに入れ替えます。これで設定は完了です。
use App\Notifications\SendWelcomeNotification;
//略
// SendWelcomeMail::dispatch($user)->delay(now()->addMinutes(3));
$user->notify(new SendWelcomeNotification);
再度ユーザ登録画面からユーザの登録を行ってください。Jobでメールを送信した時とどうように現在のNotificationの設定ではキューは利用されずユーザ登録のRegisterボタンを押してから次の画面に移動するまでに2〜3秒かかります。
キューを利用する方法
キューを利用する場合はSendWelcomeNotificationファイルでインターフェイスのShouldQueueを継承される必要があります。Queableのtraitはデフォルトで設定されているのでNotificationでキューを利用するための設定はこれで完了です。
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class SendWelcomeNotification extends Notification implements ShouldQueue
{
use Queueable;
再度ユーザ登録を行ってください。キューを利用しているのでユーザ登録画面でRegisterボタンをクリックするとすぐに次の画面に移動し、ワーカーを起動している場合は処理のメッセージが表示されます。もしワーカーが起動していない場合はSQLiteのjobsテーブルを確認すると未実行のNotificationの情報が保存されています。その場合はワーカーを起動するとNotificationの処理が行われユーザにメールが通知されます。
キューにredisを利用した場合
キューにredisを利用する場合は、.envファイルを更新する必要があります。
redisのインストールに関しては下記を参照してください。
QUEUE_CONNECTIONをredisに変更します。REDIS_HOSTなどredisに関する設定がありますが、デフォルトのまま設定の変更を行っていなければそのままの値で変更する必要はありません。
QUEUE_CONNECTION=redis
・
・
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
Predisパッケージのインストール
LaravelのQueueとしてredisを使用するためには、Predisパッケージをインストールする必要があります。composerコマンドを使用してインストールを行います。
$ composer require predis/predis
Using version ^1.1 for predis/predis
処理の実行
ジョブなどの設定は何も設定変更を行わず、ユーザの登録を行います。redisに変更されたことを確認するためにワーカーを起動している場合は一度停止してください。
ユーザ登録後にGUIツール(TablePlus)を使ってredisに接続し、ジョブの情報が登録されていないか確認を行います。
redis上のdb0にLaravelのジョブ情報が登録されていることがわかります。
キュー(redis上)に登録されたジョブを実行するためにワーカーを起動します。起動と同時にジョブが実行されるのがわかります。
$ php artisan queue:work
[2019-08-27 02:09:05][mnXLozJ5R3hEeV4FZrTIhhDEwQmCl45i] Processing: App\Jobs\SendWelcomeMail
[2019-08-27 02:09:08][mnXLozJ5R3hEeV4FZrTIhhDEwQmCl45i] Processed: App\Jobs\SendWelcomeMail
Processedでジョブの処理が完了していることを確認後、Mailtrapを見るとWelcomeメールが届いていることを確認することができます。また、ジョブの処理完了後redisに登録されたジョブもなくなります。
まとめ
このようにキューとジョブを利用するとことでユーザ登録時のユーザの待ちのストレスを解消することができました。キューとジョブをうまく活用することでアプリケーション中での処理待ちを減らすことができます。
本文書を理解できたら下記の文書を読み進めることをおすすめしています。