はじめに
今回はLaravelで非同期処理を実装してみました。
ジョブ、キュー、ワーカー、キューイングといった単語が出てきます。
目標
適当な非同期処理をジョブ、キュー、ワーカーを使って実装し、
Laravelでキューイングの流れを把握します。
背景
仕事で、定期実行でないバックグラウンド処理がしたくて、キューイングにたどり着きました。
サイトからCSV(1万行以上)をアップロードして、
その個々のデータに対して追加の処理をする状況でした。
アップロードしながら追加で処理してDBへ保存…
なんて、ユーザーからしたらたまったもんじゃないですからね💦
開発環境
準備する
ジョブやキューを使用するにあたって追加の操作が必要です。
今回、キューはデータベースを使うことにしました。
ジョブ用のテーブルの作成
php artisan queue:table php artisan queue:failed-table // これいらないかも php artisan migrate
jobs
とfailed_jobs
というテーブルができたことを確認します。
.env修正
QUEUE_CONNECTION=database または QUEUE_DRIVER=database
Laravel 8.x なんですが、バージョンによって違うかもです。
ほかにredis
、beanstalkd
、sqs
、sync
が選択できます。
デフォルトはsync
(同期処理)です。
config/queue.php修正(確認)
'default' => env('QUEUE_CONNECTION', 'database'), // <= ここも変えた 'connections' => [ ..... ], 'failed' => [ ..... ],
connections
でそれぞれのキューの設定ができます。
今はそのままで行きます。
ジョブを作成する
php artisan make:job SampleJob
上記のコマンドでapp/Jobs/SampleJob.php
ができます。
引数で渡した配列をログに書き出す簡単なジョブにしてみます。
namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; class SampleJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; // ジョブのリトライ回数 public $tries = 3; // 引数の入れ物 private $arr public function __construct(array $arr) { $this->arr = $arr; /** * 以下のようにして遅延時間を設定できる。 * dispatch時に指定が無ければ10秒後に実行される。 * デフォルトは即時実行(delay(0)) */ $this->delay(10); } public function handle() { // ここに非同期処理の内容をかく。 Log::info("SampleJob実行", $this->arr); } }
Logファサードは第二引数に配列が渡せます。(もちろん文字列も)
配列で渡すと、JSON形式でログに書き込まれます。
トリガーを作成する
ジョブをキューに追加(エンキュー)するだけの簡単なAPIを用意しました。
php artisan make:controller SampleController --api
でREST APIを作成し、以下のようにしました。
use App\Jobs\SampleJob; // ... 省略 ... class SampleController extends Controller public function index() { $arr = ['message' => 'てすとだよ', 'name' => 'キューイング']; SampleJob::dispatch($arr) ->delay(5) ->onQueue('sample'); return $arr; } }
ルーティングroutes/api.php
は
Route::apiResource('sample', 'SampleController');
にしました。
これでGET /api/sample
すると、ジョブがキューに追加されます。
- dispatch( )
エンキューしてます。引数もここで渡します。
- delay( int )
何秒後にジョブを実行するのか指定できます。
未指定の場合、ジョブのコンストラクタで$this->delay(10)
としたので、10秒後に実行されます。
つまり疑似的にデフォルト遅延時間が設定でき、上書きもできます。
(今回は「5秒後」に上書きしてます)
- onQueue( string )
キューの名前を指定します。
データベースのjobsテーブルのカラム定義を見てみるとそれっぽいのがあります。
未指定の場合、default
という名前のキューに追加されます。
実行する
ジョブをキューに追加
ポストマンなどでAPIを実行GET /api/sample
してDBのjobs
テーブルを見ると
queue = 'sample'
のジョブが1つあることが確認できると思います。
これでOKですが、
このままだとジョブはずっと待機状態のまま処理されません。
ワーカーを起動
コマンドプロンプト等で以下のコマンドを打ち、
ジョブを処理するワーカーを起動します。
php artisan queue:work --queue=sample
オプション--queue
でキュー名を指定します。(onQueue
の値)
未指定の場合は--queue=default
になります。
ワーカーを起動してからAPIを実行しても大丈夫です。
- 複数のキューを指定する場合
php artisan queue:work --queue=sample,default,other
のようにカンマで区切ります。
- ジョブSampleJob.phpを変更した場合
ワーカーを再起動し、変更を読み込ませる必要があります。
開発中は面倒だと思うので、以下のように起動すれば再起動しなくてもよくなります。
PCによっては負荷がかかりすぎる場合もあるので、様子を見ながら。
php artisan queue:listen --queue=sample,default,other
- ワーカーを裏で常駐させる場合
またやります。
動作を確認する
ワーカーを起動したコマンドプロンプトに何か出てると思います。
成功した場合
storage/logs/laravel.log
あたりにログが書き込まれています。
失敗した場合
※画像は一部加工しています
3回やっても失敗したのでFailed
になりました。
リトライ回数はジョブ作成時に
public $tries = 3;
としましたね。
失敗したジョブはfailed_jobs
テーブルに入ります。
さっき急いで画像撮ってきたからちょっと恥ずかしい…笑
失敗したジョブの再実行(おまけ)
失敗したジョブのIDを指定してコマンドで再度キューに突っ込み、
ワーカーに処理させます。
- 失敗したジョブの確認
php artisan queue:failed +-----------------------------+------------+--------+--------------------+-------------+ | ID | Connection | Queue | Class | Failed At | +-----------------------------+------------+--------+--------------------+-------------+ | 18516a07-4a14-43fa-XXXX-... | database | sample | App\Jobs\SampleJob | 失敗した日時 | +-----------------------------+------------+--------+--------------------+-------------+
- キューに追加
# 選んで追加 artisan queue:retry 18516a07-4a14-43fa-XXXX-... # 全部追加 artisan queue:retry all The failed job [18516a07-4a14-43fa-XXXX-...] has been pushed back onto the queue!
この時指定するジョブIDはfailed_jobs
テーブルのuuidカラムの値です。
そしてジョブがfailed_jobs
からjobs
に移動してます。
ちなみに、以下をすると逆のことができます。
# 失敗ジョブを選んで消す php artisan queue:forget {JOB_ID(uuid)} # 失敗ジョブを全部消す php artisan queue:flush
- ワーカー起動
起動してあればそのままで、
コマンドプロンプトに何か表示されてると思います。
まとめ
Laravelでジョブ、キュー、ワーカーを使って非同期処理するときの流れは
1. ジョブ用のテーブルをマイグレーションし
2. .env
やconfig/queue.php
で設定まわりを整え
3. app\Jobs
にジョブを作成し
4. ジョブをキューに追加するトリガーを用意し
5. ワーカーを起動
でした。
Redisを使った場合や、ワーカーを裏で常駐させる方法も、やりたいところです。
動かしながらやったからか、すんなり理解できてよかったです。
最後までお読みいただき、ありがとうございました👋🏻
誤字脱字は見つけ次第修正します。
こんなに長くなるはずじゃなかった…