キュー
はじめに
ウェブアプリケーションを構築する際、通常のウェブリクエスト中に実行するのに時間がかかりすぎるような、アップロードされたCSVファイルの解析や保存などのタスクがあるかもしれません。幸いにも、Laravelでは、バックグラウンドで処理されるキューイングジョブを簡単に作成することができます。時間のかかるタスクをキューに移動することで、アプリケーションは高速にウェブリクエストに応答し、顧客により良いユーザーエクスペリエンスを提供することができます。
Laravelのキューは、Amazon SQS、Redis、またはリレーショナルデータベースなど、さまざまなキューバックエンドにわたる統一されたキューイングAPIを提供します。
Laravelのキューの設定オプションは、アプリケーションの config/queue.php
構成ファイルに保存されています。このファイルには、フレームワークに含まれる各キュードライバーの接続構成が含まれており、データベース、Amazon SQS、Redis、Beanstalkd ドライバー、および即座にジョブを実行する同期ドライバーが含まれています(ローカル開発時に使用)。また、ジョブを破棄する null
キュードライバーも含まれています。
Laravelには、Redisを使用したキューのための美しいダッシュボードと構成システムであるHorizonが提供されています。詳細については、Horizonのドキュメントをご覧ください。
接続とキュー
Laravelキューを始める前に、「接続」と「キュー」の違いを理解することが重要です。config/queue.php
構成ファイルには、connections
構成配列があります。このオプションは、Amazon SQS、Beanstalk、またはRedisなどのバックエンドキューサービスへの接続を定義します。ただし、特定のキュー接続には複数の「キュー」が存在する可能性があり、これは異なるスタックや積まれたジョブの山と考えることができます。
queue
構成ファイル内の各接続構成例には、queue
属性が含まれています。これは、ジョブが特定の接続に送信されたときにディスパッチされるデフォルトのキューを定義します。言い換えると、ジョブを明示的にどのキューにディスパッチするかを定義しないでジョブをディスパッチした場合、そのジョブは接続構成の queue
属性で定義されたキューに配置されます。
use App\Jobs\ProcessPodcast;
// This job is sent to the default connection's default queue...
ProcessPodcast::dispatch();
// This job is sent to the default connection's "emails" queue...
ProcessPodcast::dispatch()->onQueue('emails');
一部のアプリケーションでは 、複数のキューにジョブを常にプッシュする必要がない場合があり、代わりに単純なキューを持つことを好むかもしれません。ただし、ジョブを複数のキューにプッシュすることは、ジョブの処理方法を優先順位付けしたりセグメント化したいアプリケーションに特に役立ちます。なぜなら、Laravelのキューワーカーは、処理するキューを優先順位で指定できるからです。たとえば、high
キューにジョブをプッシュすると、それらをより高い処理優先度で処理するワーカーを実行できます:
php artisan queue:work --queue=high,default
ドライバーの注意事項と前提条件
データベース
database
キュードライバーを使用するには、ジョブを保持するためのデータベーステーブルが必要です。通常、これはLaravelのデフォルトの 0001_01_01_000002_create_jobs_table.php
データベースマイグレーション に含まれていますが、アプリケーションにこのマイグレーションが含まれていない場合は、make:queue-table
Artisanコマンドを使用して作成できます:
php artisan make:queue-table
php artisan migrate
Redis
redis
キュードライバーを使用するには、config/database.php
構成ファイルで Redis データベース接続を構成する必要があります。
serializer
および compression
Redis オプションは redis
キュードライバーではサポートされていません。
Redis クラスター
Redis キュー接続が Redis クラスターを使用している場合、キュー名に キーハッシュタグ を含める必要があります。これは、特定のキューのすべての Redis キーが同じハッシュスロットに配置されるようにするために必要です:
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', '{default}'),
'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
ブロッキング
Redis キューを使用する場合、block_for
構成オプションを使用して、ジョブが利用可能になるまでドライバーが待機する時間を指定できます。これにより、ワーカーループを繰り返し、Redis データベースを再ポーリングするよりも、キューロードに基づいてこの値を調整することが効率的です。たとえば、ジョブが利用可能になるまでドライバーが 5 秒間ブロックするように値を設定することができます:
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => 5,
'after_commit' => false,
],
block_for
を 0
に設定すると、ジョブが利用可能になるまでキューのワーカーが無期限にブロックされます。これにより、SIGTERM
などのシグナルが次のジョブが処理されるまで処理されなくなります。
その他のドライバーの前提条件
以下の依存関係が、リストされたキュードライバーに必要です。これらの依存関係は、Composer パッケージマネージャーを介してインストールできます:
- Amazon SQS:
aws/aws-sdk-php ~3.0
- Beanstalkd:
pda/pheanstalk ~5.0
- Redis:
predis/predis ~2.0
または phpredis PHP 拡張機能
ジョブの作成
ジョブクラスの生成
デフォルトでは、アプリケーションのすべてのキュー可能なジョブは app/Jobs
ディレクトリに保存されます。app/Jobs
ディレクトリが存在しない場合は、make:job
Artisan コマンドを実行すると作成されます:
php artisan make:job ProcessPodcast
生成されたクラスは Illuminate\Contracts\Queue\ShouldQueue
インターフェースを実装し、ジョブが非同期で実行されるべきであることを Laravel に示します。
ジョブスタブは スタブの公開 を使用してカスタマイズできます。
クラス構造
ジョブクラスは非常にシンプルで、通常は、ジョブがキューによって処理されるときに呼び出される handle
メソッドのみを含みます。始めるために、例としてジョブクラスを見てみましょう。この例では、ポッドキャスト配信サービスを管理し、公開前にアップロードされたポッドキャストファイルを処理する必要があるとします:
<?php
namespace App\Jobs;
use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(
public Podcast $podcast,
) {}
/**
* Execute the job.
*/
public function handle(AudioProcessor $processor): void
{
// Process uploaded podcast...
}
}
この例では、キューにジョブを直接渡すことができることに注意してください。ジョブが使用している SerializesModels
トレイトのおかげで、Eloquent モデルとその読み込まれたリレーションシップは、ジョブが処理されるときに優雅にシリアライズおよび非シリアライズされます。
キューにジョブが Eloquent モデルをコンストラクタに受け入れる場合、モデルの識別子のみがキューにシリアル化されます。実際にジョブが処理されるとき、キューシステムは自動的にデータベースから完全なモデルインスタンスとその読み込まれたリレーションシップを再取得します。このモデルのシリアル化方法により、より小さなジョブペイロードをキュードライバ ーに送信できます。
#### `handle`メソッドの依存関係の注入
`handle`メソッドは、ジョブがキューで処理されるときに呼び出されます。`handle`メソッドで依存関係を型ヒントすることができることに注意してください。Laravelの[サービスコンテナ](/docs/container)は、これらの依存関係を自動的に注入します。
コンテナが`handle`メソッドに依存関係を注入する方法を完全に制御したい場合は、コンテナの`bindMethod`メソッドを使用できます。`bindMethod`メソッドは、ジョブとコンテナを受け取るコールバックを受け入れます。コールバック内では、`handle`メソッドを自由に呼び出すことができます。通常、このメソッドを`App\Providers\AppServiceProvider`の`boot`メソッドから呼び出すべきです[サービスプロバイダ](/docs/providers):
```php
use App\Jobs\ProcessPodcast;
use App\Services\AudioProcessor;
use Illuminate\Contracts\Foundation\Application;
$this->app->bindMethod([ProcessPodcast::class, 'handle'], function (ProcessPodcast $job, Application $app) {
return $job->handle($app->make(AudioProcessor::class));
});
生の画像コンテンツなどのバイナリデータは、キューに配置される際にJSONに適切にシリアル化されない可能性があるため、キューに渡す前にbase64_encode
関数を通す必要があります。
キュー関係
ジョブがキューに配置されると、読み込まれたEloquentモデルの関係もシリアル化されるため、シリアル化されたジョブ文字列はしばしば非常に大きくなります。さらに、ジョブがデシリアライズされ、モデルの関係が再度データベースから再取得されると、それらは完全に取得されます。モデルがジョブキューイングプロセス中にシリアル化される前に適用されていた以前の関係制約は、ジョブがデシリアライズされるときに適用されません。したがって、特定の関係のサブセットで作業する場合は、キューに入れたジョブ内でその関係を再制約する必要があります。
また、関係がシリアル化されないようにするために、プロパティ値を設定するときにモデルでwithoutRelations
メソッドを呼び出すことができます。このメソッドは、読み込まれた関係を持たないモデルのインスタンスを返します:
/**
* Create a new job instance.
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast->withoutRelations();
}
PHPのコンストラクタプロパティプロモーションを使用しており、Eloquentモデルに関係がシリアル化されないようにする場合は、WithoutRelations
属性を使用できます。
use Illuminate\Queue\Attributes\WithoutRelations;
/**
* Create a new job instance.
*/
public function __construct(
#[WithoutRelations]
public Podcast $podcast
) {
}
ジョブが1つのモデルではなく、Eloquentモデルのコレクションや配列を受け取った場合、そのコレクション内のモデルは、ジョブが逆シリアル化されて実行されるときに関連付けが復元されません。これは、大量のモデルを扱うジョブで過剰なリソース使用を防ぐためです。
ユニークなジョブ
ユニークなジョブには、ロックをサポートするキャッシュドライバが必要です。現在、memcached
、redis
、dynamodb
、database
、file
、array
キャッシュドライバがアトミックロックをサポートしています。また、ユニークなジョブの制約はバッチ内のジョブには適用されません。
時々、特定のジョブのインスタンスが常にキューに1つだけ存在することを確認したい場合があります。そのような場合は、ジョブクラスでShouldBeUnique
インターフェースを実装することで実現できます。このインターフェースでは、クラスに追加のメソッドを定義する必要はありません。
<?php
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
...
}
上記の例では、UpdateSearchIndex
ジョブはユニークです。そのため、既にキューに同じジョブのインスタンスが存在し、処理が完了していない場合は、ジョブはディスパッチされません。
特定の場合には、ジョブを一意にする特定の「キー」を定義したり、ジョブが一意でなくなるタイムアウトを指定したりしたい場合があります。これを実現するには、ジョブクラスでuniqueId
およびuniqueFor
プロパティまたはメソッドを定義することができます。
<?php
use App\Models\Product;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
/**
* The product instance.
*
* @var \App\Product
*/
public $product;
/**
* The number of seconds after which the job's unique lock will be released.
*
* @var int
*/
public $uniqueFor = 3600;
/**
* Get the unique ID for the job.
*/
public function uniqueId(): string
{
return $this->product->id;
}
}
上記の例では、UpdateSearchIndex
ジョブは製品IDによってユニークです。そのため、同じ製品IDでジョブを新たにディスパッチしても、既存のジョブが処理を完了するまで無視されます。さらに、既存のジョブが1時間以内に処理されない場合、ユニークロックが解放され、同じユニークキーを持つ別のジョブがキューにディスパッチされる可能性があります。
アプリケーションが複数のWebサーバーやコンテナからジョブをディスパッチする場合は、すべてのサーバーが同じ中央キャッシュサーバーと通信していることを確認してください。これにより、Laravelがジョブが一意かどうかを正確に判断できます。
処理が開始するまでジョブを一意に保つ
デフォルトでは、一意のジョブは処理が完了するか、リトライ試行がすべて失敗した後に「ロック解除」されます。ただし、ジョブが処理される直前にジョブをすぐにロック解除したい場合があります。これを実現するには、ジョブは ShouldBeUnique
コントラクトの代わりに ShouldBeUniqueUntilProcessing
コントラクトを実装する必要があります:
<?php
use App\Models\Product;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
// ...
}
一意のジョブロック
裏側では、ShouldBeUnique
ジョブがディスパッチされると、Laravel は uniqueId
キーで ロック を取得しようとします。ロックが取得できない場合、ジョブはディスパッチされません。このロックは、ジョブが処理を完了するか、リトライ試行がすべて失敗したときに解放されます。デフォルトでは、Laravel はこのロックを取得するためにデフォルトのキャッシュドライバを使用します。ただし、ロックを取得するために別のドライバを使用したい場合は、使用するキャッシュドライバを返す uniqueVia
メソッドを定義することができます:
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
...
/**
* Get the cache driver for the unique job lock.
*/
public function uniqueVia(): Repository
{
return Cache::driver('redis');
}
}
ジョブの同時処理を制限するだけでよい場合は、WithoutOverlapping
ジョブミドルウェアを使用してください。
暗号化されたジョブ
Laravel では、暗号化 を介してジョブのデータのプライバシーと整合性を確保することができます。開始するには、ジョブクラスに ShouldBeEncrypted
インターフェースを追加するだけです。このインターフェースがクラスに追加されると、Laravel はジョブをキューにプッシュする前に自動的にジョブを暗号化します:
<?php
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
class UpdateSearchIndex implements ShouldQueue, ShouldBeEncrypted
{
// ...
}
ジョブミドルウェア
ジョブミドルウェアを使用すると、キューに入れられたジョブの実行を囲むカスタムロジックをラップすることができ、ジョブ自体の冗長性を減らすことができます。たとえば、次の handle
メソッドを考えてみてください。このメソッドは、Laravel の Redis レート制限機能を活用して、5秒ごとに1つのジョブのみが処理されるようにします:
use Illuminate\Support\Facades\Redis;
/**
* Execute the job.
*/
public function handle(): void
{
Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () {
info('Lock obtained...');
// Handle job...
}, function () {
// Could not obtain lock...
return $this->release(5);
});
}
このコードは有効ですが、handle
メソッドの実装が Redis レート制限ロジックで混 雑しているため、ノイズが多くなります。さらに、このレート制限ロジックは、他のレート制限を行いたいジョブにも複製する必要があります。
代わりに、handle
メソッドでレート制限を行う代わりに、レート制限を処理するジョブミドルウェアを定義することができます。Laravel にはジョブミドルウェアのデフォルトの場所がないため、アプリケーションのどこにでもジョブミドルウェアを配置することができます。この例では、ミドルウェアを app/Jobs/Middleware
ディレクトリに配置します:
<?php
namespace App\Jobs\Middleware;
use Closure;
use Illuminate\Support\Facades\Redis;
class RateLimited
{
/**
* Process the queued job.
*
* @param \Closure(object): void $next
*/
public function handle(object $job, Closure $next): void
{
Redis::throttle('key')
->block(0)->allow(1)->every(5)
->then(function () use ($job, $next) {
// Lock obtained...
$next($job);
}, function () use ($job) {
// Could not obtain lock...
$job->release(5);
});
}
}
ルートミドルウェアのように、ジョブミドルウェアも処理されるジョブと、ジョブの処理を継続するために呼び出すべきコールバックを受け取ります。
ジョブミドルウェアを作成した後は、ジョブの middleware
メソッドからそれらを返すことで、ジョブにアタッチすることができます。このメソッドは make:job
Artisan コマンドで生成されたジョブには存在しないため、ジョブクラスに手動で追加する必要があります:
use App\Jobs\Middleware\RateLimited;
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [new RateLimited];
}
ジョブミドルウェアは、キュー可能なイベントリスナーやメーラブル、通知にも割り当てることができます。
レート制限
自作のレート制限ジョブミドルウェアを書く方法を示しましたが、実際には Laravel にはジョブのレート制限を行うために利用できるレート制限ミドルウェアが含まれています。ルートのレート制限と同様に、ジョブのレート制限は RateLimiter
ファサードの for
メソッドを使用して定義されます。
たとえば、ユーザーにデータのバックアップを1時間に1回だけ許可し、プレミアム顧客にはそのような制限を課さないようにしたい場合、AppServiceProvider
の boot
メソッドで RateLimiter
を定義することができます:
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
RateLimiter::for('backups', function (object $job) {
return $job->user->vipCustomer()
? Limit::none()
: Limit::perHour(1)->by($job->user->id);
});
}
上記の例では、1時間ごとのレート制限を定義しましたが、perMinute
メソッドを使用して分単位でレート制限を簡単に定義することもできます。さらに、レート制限を by
メソッドに渡すことができますが、この値は通常、顧客ごとにレート制限を区別するために使用されます:
return Limit::perMinute(50)->by($job->user->id);
レート制限を定義したら、Illuminate\Queue\Middleware\RateLimited
ミドルウェアを使用して、ジョブにレート制限をアタッチすることができます。ジョブがレート制限を超過するたびに、このミドルウェア は適切な遅延を持つジョブをキューに戻します。
リミットが設定されたジョブをキューに戻すと、ジョブの attempts
総数が増加します。ジョブクラスの tries
と maxExceptions
プロパティを調整したり、ジョブが試行されるまでの時間を定義するために retryUntil
メソッド を使用することができます。
ジョブがレート制限されたときにジョブを再試行したくない場合は、dontRelease
メソッドを使用できます:
Redis を使用している場合は、Redis に最適化された Illuminate\Queue\Middleware\RateLimitedWithRedis
ミドルウェアを使用することができます。これは、基本的なレート制限ミドルウェアよりも効率的です。
ジョブの重複を防ぐ
Laravel には、任意のキーに基づいてジョブの重複を防止する Illuminate\Queue\Middleware\WithoutOverlapping
ミドルウェアが含まれています。これは、キューに入れられたジョブが一度に 1 つだけ変更されるべきリソースを変更するときに役立ちます。
たとえば、ユーザーの信用スコアを更新するキューに入れられたジョブがあり、同じユーザー ID の信用スコア更新ジョブの重複を防止したいとします。これを実現するために、ジョブの middleware
メソッドから WithoutOverlapping
ミドルウェアを返すことができます:
use Illuminate\Queue\Middleware\WithoutOverlapping;
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [new WithoutOverlapping($this->user->id)];
}
同じタイプの重複するジョブはキューに戻されます。また、リリースされたジョブが再試行されるまでに経過する必要がある秒数を指定することもできます:
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)];
}
重複するジョブをすぐに削除して再試行されないようにしたい場合は、dontRelease
メソッドを使用できます:
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [(new WithoutOverlapping($this->order->id))->dontRelease()];
}
WithoutOverlapping
ミドルウェアは Laravel のアトミックロック機能によって動作します。時々、ジョブが予期せず失敗したりタイムアウトしたりしてロックが解放されない場合があります。そのため、expireAfter
メソッドを使用して明示的にロックの有効期限を定義することができます。たとえば、以下の例では、ジョブの処理が開始されてから 3 分後に WithoutOverlapping
ロックを解放するように Laravel に指示します:
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [(new WithoutOverlapping($this->order->id))->expireAfter(180)];
}
WithoutOverlapping
ミドルウェアは、locksをサポートするキャッシュドライバが必要です。現在、memcached
、redis
、dynamodb
、database
、file
、およびarray
キャッシュドライバがアトミックロックをサポートしています。
ジョブクラス間でのロックキーの共有
デフォルトでは、WithoutOverlapping
ミドルウェアは、同じクラスのジョブの重複を防ぎます。したがって、異なる2つのジョブクラスが同じロックキーを使用していても、重複を防ぐことはありません。ただし、shared
メソッドを使用して、Laravelにジョブクラス間でキーを適用するように指示することができます:
use Illuminate\Queue\Middleware\WithoutOverlapping;
class ProviderIsDown
{
// ...
public function middleware(): array
{
return [
(new WithoutOverlapping("status:{$this->provider}"))->shared(),
];
}
}
class ProviderIsUp
{
// ...
public function middleware(): array
{
return [
(new WithoutOverlapping("status:{$this->provider}"))->shared(),
];
}
}
例外のスロットリング
Laravelには、例外をスロットリングするIlluminate\Queue\Middleware\ThrottlesExceptions
ミドルウェアが含まれており、一定数の例外をスローした後、ジョブを実行しようとするすべての試行が指定さ れた時間間隔が経過するまで遅延されます。このミドルウェアは、不安定なサードパーティサービスとやり取りするジョブに特に有用です。
例えば、サードパーティAPIとやり取りするキューに入れられたジョブが例外をスローし始めたとします。例外をスロットリングするには、ジョブのmiddleware
メソッドからThrottlesExceptions
ミドルウェアを返すことができます。通常、このミドルウェアは、time based attemptsを実装したジョブとペアにする必要があります:
use DateTime;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [new ThrottlesExceptions(10, 5)];
}
/**
* Determine the time at which the job should timeout.
*/
public function retryUntil(): DateTime
{
return now()->addMinutes(5);
}
ミドルウェアが受け入れる最初のコンストラクタ引数は、ジョブがスローできる例外の数であり、2番目のコンストラクタ引数は、ジョブがスロットリングされた後に再試行されるまで経過するべき分数です。上記のコード例では、ジョブが5分以内に10回の例外をスローした場合、ジョブを再試行するまで5分待機します。
ジョブが例外をスローしたが、例外の閾値に達していない場合、通常はジョブがすぐに再試行されます。ただし、ミドルウェアをジョブにアタッチする際にbackoff
メソッドを呼び出すことで、そのようなジョブを遅延させる分数を指定することができます。
use Illuminate\Queue\Middleware\ThrottlesExceptions;
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [(new ThrottlesExceptions(10, 5))->backoff(5)];
}
このミドルウェアは、内部的にはLaravelのキャッシュシステムを使用してレート制限を実装 し、ジョブのクラス名がキャッシュの「キー」として使用されます。ジョブにミドルウェアをアタッチする際にby
メソッドを呼び出すことで、このキーをオーバーライドすることができます。これは、同じサードパーティーサービスとやり取りする複数のジョブがあり、それらが共通のスロットリング「バケツ」を共有したい場合に便利です。
use Illuminate\Queue\Middleware\ThrottlesExceptions;
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [(new ThrottlesExceptions(10, 10))->by('key')];
}
デフォルトでは、このミドルウェアはすべての例外に対してスロットリングを行います。when
メソッドを呼び出すことで、ジョブにミドルウェアをアタッチする際にこの動作を変更することができます。when
メソッドに提供されたクロージャがtrue
を返す場合にのみ、例外がスロットリングされます。
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [(new ThrottlesExceptions(10, 10))->when(
fn (Throwable $throwable) => $throwable instanceof HttpClientException
)];
}
スロットリングされた例外をアプリケーションの例外ハンドラに報告したい場合は、ジョブにミドルウェアをアタッチする際にreport
メソッドを呼び出すことができます。オプションで、report
メソッドにクロージャを提供し、指定されたクロージャがtrue
を返す場合にのみ例外が報告されます。
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [(new ThrottlesExceptions(10, 10))->report(
fn (Throwable $throwable) => $throwable instanceof HttpClientException
)];
}
Redisを使用している場合は、Redis向けに調整されたIlluminate\Queue\Middleware\ThrottlesExceptionsWithRedis
ミドルウェアを使用することができます。これは、基本的な例外スロットリングミドルウェアよりもRedisに適しており、効率的です。
ジョブのディスパッチ
ジョブクラスを作成したら、そのジョブ自体でdispatch
メソッドを使用してディスパッチできます。dispatch
メソッドに渡される引数は、ジョブのコンストラクタに渡されます。
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PodcastController extends Controller
{
/**
* Store a new podcast.
*/
public function store(Request $request): RedirectResponse
{
$podcast = Podcast::create(/* ... */);
// ...
ProcessPodcast::dispatch($podcast);
return redirect('/podcasts');
}
}
条件付きでジョブをディスパッチしたい場合は、dispatchIf
メソッドとdispatchUnless
メソッドを使用できます。
ProcessPodcast::dispatchIf($accountActive, $podcast);
ProcessPodcast::dispatchUnless($accountSuspended, $podcast);
新しいLaravelアプリケーションでは、sync
ドライバがデフォルトのキュードライバです。このドライバは、ジョブを現在のリクエストの前景で同期的に実行します。これは、ローカル開発中に便利です。バックグラウンド処理のためにジョブを実際にキューに入れたい場合は、アプリケーションのconfig/queue.php
構成ファイルで異なるキュードライバを指定することができます。
遅延ディスパッチ
ジョブがキューのワーカーによってすぐに処理されないように指定したい場合は、ジョブをディスパッチする際に delay
メソッドを使用できます。たとえば、ジョブがディスパッチされてから10分後まで処理できないように指定したい場合は、次のようにします:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PodcastController extends Controller
{
/**
* Store a new podcast.
*/
public function store(Request $request): RedirectResponse
{
$podcast = Podcast::create(/* ... */);
// ...
ProcessPodcast::dispatch($podcast)
->delay(now()->addMinutes(10));
return redirect('/podcasts');
}
}
Amazon SQS キューサービスの最大遅延時間は15分です。
レスポンスがブラウザに送信された後のディスパッチ
代わりに、dispatchAfterResponse
メソッドは、FastCGI を使用している場合に、HTTP レスポンスがユーザーのブラウザに送信された後にジョブをディスパッチすることを遅延させます。これにより、ユーザーはアプリケーションの使用を開始できるようになりますが、キューに入れられたジョブはまだ実行中です。これは通常、1秒程度かかるようなジョブ(たとえば、メールの送信)にのみ使用するべきです。こ の方法でディスパッチされたジョブは、現在の HTTP リクエスト内で処理されるため、それらを処理するためにはキューのワーカーが実行中である必要はありません:
use App\Jobs\SendNotification;
SendNotification::dispatchAfterResponse();
また、クロージャを dispatch
し、dispatch
ヘルパーに afterResponse
メソッドをチェーンして、HTTP レスポンスがブラウザに送信された後にクロージャを実行することもできます:
use App\Mail\WelcomeMessage;
use Illuminate\Support\Facades\Mail;
dispatch(function () {
Mail::to('taylor@example.com')->send(new WelcomeMessage);
})->afterResponse();
同期ディスパッチ
ジョブを即座に(同期的に)ディスパッチしたい場合は、dispatchSync
メソッドを使用できます。このメソッドを使用すると、ジョブはキューに入れられず、現在のプロセス内で直ちに実行されます:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class PodcastController extends Controller
{
/**
* Store a new podcast.
*/
public function store(Request $request): RedirectResponse
{
$podcast = Podcast::create(/* ... */);
// Create podcast...
ProcessPodcast::dispatchSync($podcast);
return redirect('/podcasts');
}
}