Laravel のキュー サービスは、さまざまなバックグラウンド キュー サービスに統合された API を提供します。キューを使用すると、電子メールの送信など、時間のかかるタスクの実行を遅らせることができます。これにより、リクエストの応答時間を効果的に短縮できます。この記事では、Laravel が同じキュータスクを繰り返し実行する原因を、サンプルコードを通して詳しく紹介していますので、ぜひ参考にしてみてください。
問題が見つかりました
Laravel で Redis を使用してキュータスクを処理するフレームワークが提供する機能は非常に強力ですが、最近問題が発生しました。つまり、タスクが複数回実行されることがわかりました。
まず理由について話しましょう:
Laravel では、キュー (タスク) の実行時間が 60 秒を超えると、失敗したとみなされ、キューに再度追加されます。同じタスクが繰り返し実行されることになります。
このタスクのロジックは、ユーザーにコンテンツをプッシュすることであり、ユーザーはキューのコンテンツに基づいて取得および走査され、リクエスト バックエンド HTTP インターフェイスを通じて送信される必要があります。たとえば、ユーザーが 10,000 人いる場合、ユーザー数が多かったり、インターフェイスの処理速度がそれほど速くない場合、実行時間は確実に 60 秒を超えるため、タスクは再びキューに追加されます。状況はさらに悪くなります。前のタスクが 60 秒以内に実行されなかった場合、それらのタスクはキューに再度追加され、同じタスクが 1 回だけでなく複数回実行されることになります。
Laravel のソースコードから犯人を見つけてみましょう。
ソースコードファイル:vendor/laravel/framework/src/Illuminate/Queue/RedisQueue.php
/** * The expiration time of a job. * * @var int|null */ protected $expire = 60;
この $expire メンバー変数は、60 秒かかってもキューが実行されるべきであると信じています。 。キューの取得方法:
public function pop($queue = null) { $original = $queue ?: $this->default; $queue = $this->getQueue($queue); $this->migrateExpiredJobs($queue.':delayed', $queue); if (! is_null($this->expire)) { $this->migrateExpiredJobs($queue.':reserved', $queue); } list($job, $reserved) = $this->getConnection()->eval( LuaScripts::pop(), 2, $queue, $queue.':reserved', $this->getTime() + $this->expire ); if ($reserved) { return new RedisJob($this->container, $this, $job, $reserved, $original); } }
キューを取得するにはいくつかの手順があります。キューは実行に失敗したり、実行がタイムアウトしたりするため、別のコレクションに保存され、再試行されます。
1. 実行が原因で失敗したキューを削除し、遅延したコレクションを現在実行中のキューに再プッシュします。
2. 実行タイムアウトのため、予約されたコレクションから現在実行されているキューにキューを再プッシュします。
3. 次に、キューからタスクを取得して実行を開始し、予約された順序付きコレクションにキューを入れます。
ここではこの処理を実行するために eval コマンドが使用されており、いくつかの lua スクリプトが使用されています。
実行するキューからタスクを取得します:
local job = redis.call('lpop', KEYS[1]) local reserved = false if(job ~= false) then reserved = cjson.decode(job) reserved['attempts'] = reserved['attempts'] + 1 reserved = cjson.encode(reserved) redis.call('zadd', KEYS[2], ARGV[1], reserved) end return {job, reserved}
Laravel が Redis によって実行されるキューを取得すると、コピーを順序付きセットに入れ、有効期限のタイムスタンプをスコアとして使用することがわかります。
タスクが完了した場合にのみ、順序付きセットからタスクを削除します。この順序付けされたコレクションからキューを削除するコードは省略されています。Laravel が実行時間が 60 秒を超えるキューをどのように処理するかを見てみましょう。
これがこの Lua スクリプトの動作です:
local val = redis.call('zrangebyscore', KEYS[1], '-inf', ARGV[1]) if(next(val) ~= nil) then redis.call('zremrangebyrank', KEYS[1], 0, #val - 1) for i = 1, #val, 100 do redis.call('rpush', KEYS[2], unpack(val, i, math.min(i+99, #val))) end end return true
ここで、zrangebyscore は、infinitesimal から現在のタイムスタンプまでのスコアを持つ要素 (つまり、60 秒前にセットに追加されたタスク) を検索し、zremrangebyrank 要素を通じてこれらをセットから削除します。そしてそれをキューにプッシュします。
これを見てハッと気づくはずです。
キューが 60 秒以内に実行されない場合、プロセスはキューをフェッチするときに予約セットからタスクを再度キューにプッシュします。
関連する推奨事項:
Laravel 5のパフォーマンスを向上させるための実践的なヒント
laravel開発におけるクロスドメインソリューションの詳細な説明
以上がLaravelが同じキュータスクを繰り返し実行する理由の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。