イントロダクション
Laravelのキューサービスは、様々なキューバックエンドに対し共通のAPIを提供しています。キューによりメール送信のような時間を費やす処理を遅らせることが可能です。これによりアプリケーションのリクエストを徹底的に引き上げることができます。
設定
キューの設定ファイルはconfig/queue.php
です。このファイルにはフレームワークに含まれているそれぞれのドライバーへの接続設定が含まれています。それにはデータベース、Beanstalkd、Amazon SQS、Redis、同期(ローカル用途)ドライバーが含まれています。
null
キュードライバはキューされたジョブが実行されないように、破棄するだけです。
ドライバ毎の必要要件
データベース
database
キュードライバを使用するには、ジョブを記録するためのデータベーステーブルが必要です。このテーブルを作成するマイグレーションはqueue:table
Artisanコマンドにより生成できます。マイグレーションが生成されたら、migrate
コマンドでデータベースをマイグレートしてください。
php artisan queue:table
php artisan migrate
他のドライバに必要なパッケージ
以下の依存パッケージがリストしたキュードライバを使用するために必要です。
- Amazon SQS:
aws/aws-sdk-php ~3.0
- Beanstalkd:
pda/pheanstalk ~3.0
- Redis:
predis/predis ~1.0
ジョブクラスを書く
ジョブクラスの生成
キュー投入可能なアプリケーションの全ジョブは、デフォルトでapp/Jobs
ディレクトリへ保存されます。新しいキュー投入ジョブはArtisan
CLIで生成できます。
php artisan make:job SendReminderEmail
このコマンドにより新しいクラスがapp/Jobs
ディレクトリに生成され、そのクラスは同期的に実行する代わりにキューへジョブを投入することをLaravelに知らせる目印となる、Illuminate\Contracts\Queue\ShouldQueue
インターフェイスを実装しています。
ジョブクラスの構造
ジョブクラスはとてもシンプルでキューでジョブが処理されるときに呼び出されるhandle
メソッドだけで通常構成されています。手始めにこのジョブクラスを確認してみましょう。
<?php
namespace App\Jobs;
use App\User;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendReminderEmail extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $user;
/**
* 新しいジョブインスタンスの生成
*
* @param User $user
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* ジョブの実行
*
* @param Mailer $mailer
* @return void
*/
public function handle(Mailer $mailer)
{
$mailer->send('emails.reminder', ['user' => $this->user], function ($m) {
//
});
$this->user->reminders()->create(...);
}
}
この例中、キュージョブのコンテナーに直接Eloquentモデルが渡せることに注目してください。ジョブが使用しているSerializesModels
トレイトによりEloquentモデルは優雅にシリアライズされ、ジョブが処理される時にアンシリアライズされます。キュー投入されたジョブがコンテナでEloquentモデルを受け取ると、モデルの識別子のみシリアライズされています。ジョブが実際に処理される時、キューシステムは自動的にデータベースから完全なモデルインスタンスを再取得します。これらは全てアプリケーションの完全な透過性のためであり、Eloquentモデルインスタンスをシリアライズするときに発生する問題を防ぐことができます。
handle
メソッドはキューによりジョブが処理されるときに呼びだされます。ジョブのhandle
メソッドにタイプヒントにより依存を指定できることに注目してください。Laravelのサービスコンテナが自動的に依存を注入します。
上手く行かない場合
ジョブの実行時に例外が投げられると、再実行を試みるため自動的にキューへ戻されます。ジョブはアプリケーションが許している最大回数まで続けて再実行されます。最大再実行回数はqueue:listen
かqueue:work
Artisanコマンド実行時に--tries
スイッチで指定します。キューリスナの詳細はこの後に記述します。
ジョブを手動でリリースする
ジョブを自分で再実行したい場合、生成したジョブクラスでは含まれているInteractsWithQueue
トレイトが提供するキュージョブのrelease
メソッドを使用してください。release
メソッドは引数をひとつだけ取り、そのジョブを再実行可能にするまで待機する秒数を指定します。
public function handle(Mailer $mailer)
{
if (condition) {
$this->release(10);
}
}
実行試行回数のチェック
前述の通り、ジョブの処理中に例外が起きた場合、そのジョブは自動的にキューに再登録されます。ジョブを実行しようとした試行回数をattempts
メソッドで調べることができます。
public function handle(Mailer $mailer)
{
if ($this->attempts() > 3) {
//
}
}
ジョブのキュー投入
デフォルトLaravelコントローラーのapp/Http/Controllers/Controller.php
はDispatchesJobs
トレイトを使っています。このトレイトはdispatch
メソッドのような、キューへジョブを便利に投入できるようにいくつかのメソッドを提供しています。
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* 指定ユーザーにリマインダーメールを送信する
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$this->dispatch(new SendReminderEmail($user));
}
}
DispatchesJobs
トレイト
もちろんルートやコントローラーではないアプリケーションのどこからか、ジョブをディスパッチしたいこともあるでしょう。そのためDispatchesJobs
トレイトはアプリケーションのどのクラスでも使えるようになっており、多くのディスパッチメソッドにアクセスできます。このトレイトを使用するサンプルクラスを見てください。
<?php
namespace App;
use Illuminate\Foundation\Bus\DispatchesJobs;
class ExampleClass
{
use DispatchesJobs;
}
dispatch
関数
もしくは、dispatch
グローバル関数を使うこともできます。
Route::get('/job', function () {
dispatch(new App\Jobs\PerformTask);
return 'Done!';
});
ジョブのキュー指定
さらに特定のキューにジョブを送ることもできます。
ジョブを異なったキューに送ることでキューするジョブを「カテゴリー分け」できます。さらに様々なキューにいくつのワーカーを割りつけるかによりプライオリティー付けできます。これはキュー設定ファイルで定義されている別々のキュー「接続」へジョブを送るという意味ではなく、一つの接続に対しキューを指定するだけで実現できます。キューを指定するにはジョブインスタンスのonQueue
メソッドを使います。onQueue
メソッドはApp\Jobs\Job
基礎クラスに含まれる、Illuminate\Bus\Queueable
トレイトにより提供しています。
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* 指定ユーザにリマインダーメールを送信する
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->onQueue('emails');
$this->dispatch($job);
}
}
注意:
DispatchesJobs
トレイトは、デフォルトキュー接続のキューへジョブを投入します。
ジョブのキュー接続指定
複数のキュー接続を取り扱う場合、ジョブへ投入する接続を指定する必要が起きます。接続の指定には、ジョブインスタンスのonConnection
メソッドを使います。onConnection
メソッドはIlluminate\Bus\Queueable
トレイトにより提供されており、App\Jobs\Job
ベースクラスで取り込まれています。
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* 指定したユーザへリマインダーメールを送信
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->onConnection('alternate');
$this->dispatch($job);
}
}
もちろん、ジョブのキューと接続を指定するために、onConnection
とonQueue
メソッドをチェーンすることもできます。
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))
->onConnection('alternate')
->onQueue('emails');
$this->dispatch($job);
}
遅延ジョブ
投入したキュージョブの実行を遅らせたい場合もあるでしょう。たとえばサインアップの5分後に顧客へメールを送信するジョブをキューしたい場合などです。この場合、Illuminate\Bus\Queueable
トレイトが提供しているdelay
メソッドを使用して下さい。
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* 指定したユーザへリマインダーメールを送信
*
* @param Request $request
* @param int $id
* @return Response
*/
public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->delay(60 * 5);
$this->dispatch($job);
}
}
この例では、ワーカーで実行可能になるまで、キューの中のジョブを5分間遅延させると指定しています。
注目: Amazon SQSサービスには、遅延900秒(15分)という制限があります。
ジョブイベント
ジョブのライフサイクルイベント
Queue::before
とQueue::after
メソッドにより、キュージョブが実行開始した時と、成功裏に完了した時点で実行されるコールバックを登録することができます。コールバックにより、追加のログを残したり、続いて実行すべきジョブをキューへ投入したり、ダッシュボード上の回数を増加させたりする、タイミングが提供されます。例として、Laravelに含まれているAppServiceProvider
で、このイベントにコールバックを指定してみます。
<?php
namespace App\Providers;
use Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobProcessed;
class AppServiceProvider extends ServiceProvider
{
/**
* アプリケーションサービスの初期起動処理
*
* @return void
*/
public function boot()
{
Queue::after(function (JobProcessed $event) {
// $event->connectionName
// $event->job
// $event->data
});
}
/**
* サービスプロバイダの登録
*
* @return void
*/
public function register()
{
//
}
}
キューリスナの実行
キューリスナの起動
LaravelのArtisanは新しくキューに保存されたジョブを実行するコマンドを含んでいます。queue:listen
コマンドを使いリスナを実行できます。
php artisan queue:listen
リスナに使用するキュー接続を指定することもできます。
php artisan queue:listen connection-name
このタスクを一度開始したら、手動で停止しない限り実行を続けることに注意してください。Supervisorのようなプロセスモニターを利用し、キューリスナが確実に動作し続けるようにしてください。
キューの優先度
listen
コマンドにキューのプライオリティーを設定するため、キュー接続をカンマ区切りで指定することもできます。
php artisan queue:listen --queue=high,low
この例でhigh
はlow
のジョブを実行する前にいつも処理されます。
ジョブのタイムアウトパラメータ指定
それぞれのジョブの実行時間を秒数で指定することもできます。
php artisan queue:listen --timeout=60
キュー休止時間の指定
さらに新しいジョブをポーリングする前に、待ち秒数を指定することもできます。
php artisan queue:listen --sleep=5
キューにジョブがない場合のみキューがスリープすることに注意して下さい。もしジョブが存在しているなら、キューはスリープせずに処理を続けます。
キュー上の最初のジョブの実行
キューの最初のジョブだけを実行するには、queue:work
コマンドを使います。
php artisan queue:work
Supervisor設定
SupervisorはLinuxオペレーティングシステムの監視プロセスで、queue:listen
やqueue:work
コマンドが落ちていれば自動的に再起動してくれます。UbuntuにSupervisorをインストールするには、次のコマンドで行います。
sudo apt-get install supervisor
Supervisorの設定ファイルは通常/etc/supervisor/conf.d
ディレクトリに保存します。このディレクトリの中には、Supervisorにどのようにプロセスを監視するのか指示する設定ファイルを好きなだけおくことができます。たとえば、laravel-worker.conf
ファイルを作成し、queue:work
プロセスを起動、監視させてみましょう。
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 --daemon
autostart=true
autorestart=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log
この例のnumprocsディレクティブはSupervisorに全部で8つのqueue:workプロセスを実行・監視し、落ちていれば自動的に再起動するように指示しています。もちろんcommand
ディレクティブのqueue:work sqs
の部分を変更し、選択したドライバに合わせてください。
設定ファイルができたら、Supervisorの設定を更新し起動するために以下のコマンドを実行してください。
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
Supervisorの設定と使用法の詳細は、Supervisorのドキュメントを読んでください。もしくは便利なWebインターフェイスからSupervisorを設定、管理できるLaravel Forgeを使うこともできます。
デーモンキューリスナ
queue:work
はフレームワークを再起動せずに連続してジョブを処理し続けるようにキューワーカーを強制するために--daemon
オプションも備えています。これによりqueue:listen
と比較すると、CPU使用率を大幅に引き下げることができます。
キュー・ワーカーをデーモンモードで開始するためには、--daemon
フラッグを使用します。
php artisan queue:work connection-name --daemon
php artisan queue:work connection-name --daemon --sleep=3
php artisan queue:work connection-name --daemon --sleep=3 --tries=3
ご覧の通りqueue:work
コマンドはqueue:listen
で使用できるものと、ほぼ同じオプションをサポートしています。php artisan help queue:work
コマンドで全オプションを表示できます。
デーモンキューワーカー使用時のコーディング留意点
デーモンキューワーカーは各ジョブを処理する前にフレームワークを再起動しません。そのため多くのリソースを使用するジョブを完了する前に、それらを開放するように気をつけてください。たとえばGDライブラリーを使用し画像処理を行う場合、完了したらimagedestroy
でメモリを開放する必要があるでしょう。
デーモンキューリスナのデプロイ
デーモンキューワーカーは長時間起動するプロセスですので、リスタートしなければコードの変更が反映されません。そのためデーモンキューワーカーを使用しているアプリケーションをデプロイする簡単な方法は、スクリプトをデプロイしている間にワーカーをリスタートすることです。デプロイスクリプトに以下のコマンドを含めることで、全スクリプトを穏やかにリスタートできます。
php artisan queue:restart
このコマンドは存在しているジョブが失われないように、現在のジョブの処理が終了した後に、全キューワーカーへ穏やかに「終了する(die)」よう指示します。キューワーカーはqueue:restart
コマンドが実行されると、終了することを思い出してください。ですから、キュージョブを自動的に再起動する、Supervisorのようなプロセスマネージャーを実行すべきでしょう。
注意: このコマンドは再起動のスケジュールするため、キャッシュシステムを利用しています。デフォルト状態ではAPCuはCLIコマンドのために動作しません。APCuを使用する場合は
apc.enable_cli=1
をAPCu設定に追加してください。
失敗したジョブの処理
物事は計画通りうまく行かない場合もありますので、キュージョブが失敗することも想定できます。でも心配ありません。最高な人たちも失敗はするものです! Laravelは指定した回数ジョブを再実行する便利な方法を用意しています。この回数実行してもうまく行かない場合は、failed_jobs
テーブルに登録されます。失敗したジョブのテーブル名はconfig/queue.php
設定ファイルで指定できます。
failed_jobs
テーブルのマイグレーションを生成するにはqueue:failed-table
コマンドを実行して下さい。
php artisan queue:failed-table
キューリスナを実行時にqueue:listen
コマンドに--tries
スイッチを使い、ジョブの最大試行回数を指定することもできます。
php artisan queue:listen connection-name --tries=3
ジョブ失敗イベント
キュージョブが失敗した時に呼び出されるイベントのリスナを登録したい場合は、Queue::failing
メソッドを使って下さい。このイベントはメールかHipChatであなたのチームに通知するのに便利でしょう。例としてLaravelに含まれているAppServiceProvider
にこのイベンのコールバックを追加してみましょう。
<?php
namespace App\Providers;
use Queue;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* アプリケーションサービスの初期起動処理
*
* @return void
*/
public function boot()
{
Queue::failing(function (JobFailed $event) {
// $event->connectionName
// $event->job
// $event->data
});
}
/**
* サービスプロバイダー登録
*
* @return void
*/
public function register()
{
//
}
}
ジョブクラスのfailedメソッド
更に細かくコントロールするために、failed
メソッドをキュージョブクラスへ直接定義することもできます。ジョブが失敗した時に特定のジョブアクションを実行できるようにできます。
<?php
namespace App\Jobs;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendReminderEmail extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
/**
* ジョブの実行
*
* @param Mailer $mailer
* @return void
*/
public function handle(Mailer $mailer)
{
//
}
/**
* 失敗したジョブの処理
*
* @return void
*/
public function failed()
{
// ジョブが失敗した時に呼び出される
}
}
失敗したジョブの再実行
failed_jobs
データベーステーブルに挿入された、失敗したジョブを全部確認したい場合はqueue:failed
Arisanコマンドを利用します。
php artisan queue:failed
queue:failed
コマンドにジョブIDを指定すれば、接続、キュー、失敗した時間がリストされます。ジョブIDは失敗したジョブを再実行する場合にも使用します。たとえばIDが5の失敗したジョブを再実行するには、以下のコマンドを実行します。
php artisan queue:retry 5
失敗したジョブをすべて再試行するには、queue:retry
でIDの代わりにall
を指定します。
php artisan queue:retry all
失敗したジョブを削除するにはqueue:forget
コマンドを使います。
php artisan queue:forget 5
失敗したジョブを全部消去するにはqueue:flush
コマンドを使用します。
php artisan queue:flush