九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、最近では「クラウド」という言葉は技術者なら誰でも知っているものになりました。
初めてこの言葉を聞いたときは「クラウド → 雲 → 雨が降る → いや、それ不吉じゃない??」と思ったものですが😂、インターネットを介して必要な処理だけを行うというやり方は、市場規模の推移を見るとどれだけ人気か分かります。
そして、私の場合も例に漏れずAWS
を活用する開発もご依頼いただくようになったのですが、その結果必要となってきたことがあります。
それが・・・・・・
(PHPの)非同期通信
です。
非同期通信というのは、シンプルに言うと「それを待ってなくてもOK」な通信のことで、これはJavaScript
のaxios
などでよく使われる言葉じゃないでしょうか(JavaScript
とPHP
の非同期はニュアンスがちょっと違いますが)
つまり、「ひとりでやるのはしんどいから、みんなで完了させよう!」という「One for all All for one」の精神ですね(笑)
メリットとしては、もちろん処理の高速化です。
例えば、2つのURLにアクセスする場合を考えてみましょう。
まずは、非同期ではない場合です。
- URL(その1)にアクセス
- 待ち
- 完了✨
- URL(その2)にアクセス
- 待ち
- 完了✨
と1つずつ「アクセス&待ち」を繰り返すため、時間がかかります。
しかし、非同期の場合は次のようになります。
- URL その1にアクセス
- 待たずにURL(その2)にアクセス
- すべてのアクセスが完了する
- 完了✨✨
どうでしょう。
「よーいドン」でアクセスするので、「待ち」の部分が重なり、その分早く処理できるというわけですね。
そうです❗
この非同期通信が使うと、クラウドへのHTTPアクセスが重複しても、処理を早くすることができるというわけです。
ということで、今回はLaravel
+Guzzle
でこの非同期通信を実装してみましょう。
何かの参考になりましたら嬉しいです。😄✨
「ユーザーは待ってくれない、
が原則です!」
開発環境: Laravel 8.x、Guzzle 7.4
目次
やりたいこと
今回実装する内容は、「複数あるURLに非同期通信する」です。
しかし、JavaScript
の非同期とは違ってPHP
は1回のアクセスで完結するので、少し違ってきます。
例えば次の場合をみてください。
- URL(その1): 処理に1秒かかる
- URL(その2): 処理に4秒かかる
- URL(その3): 処理に9秒かかる
この場合、(PHPの)非同期通信だと全部で9秒かかります。
つまり、一番長い秒数は最低でも必要になってくるというわけですね。(逆にひとつずつ待っていると、全14秒かかります)
これは、例えばAWS
とGoogle Cloud
両方に画像をアップロードして物体検出するようなイメージです。
Guzzle をインストールする
Laravel 7.x
以降はHTTP Client
としてGuzzle
を使うようになったため、初期状態でインストールされていると思います。
そのため、もしLaravel 6.x
以前の方は以下のコマンドを実行してGuzzle
をインストールしてください。
composer require guzzlehttp/guzzle
コントローラーをつくる
では、もうGuzzle
を使って非同期通信するコードをつくっていきます。
以下のコマンドでコントローラーをつくってください。
php artisan make:controller AsyncController
すると、コントローラーが作成されるので中身を以下のようにします。
app/Http/Controllers/AsyncController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use GuzzleHttp\Promise; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; use Aws\Rekognition\RekognitionClient; class AsyncController extends Controller { public function request() { $start_time = microtime(true); dump('開始'); $client = new Client(); $promises = []; for($i = 1 ; $i <= 4; $i++) { $url = route('async.response', $i); // 4つのURLを作成 $promises[] = $client->getAsync($url) // Promise(非同期通信のためのアクセス情報)をつくる ->then( function($response) use($i, $start_time) { // アクセスに成功した時 $end_time = microtime(true) - $start_time; dump('非同期通信の終了 その'. $i .': '. $end_time .' 秒'); }, function(RequestException $e) { // アクセスに失敗したとき dump($e->getMessage()); } ); } $responses = Promise\Utils::settle($promises)->wait(); dump('終了:'. microtime(true) - $start_time .' 秒'); } public function response($id) { // 重たい処理のテスト $id = intval($id); if($id === 4) { abort(404); // $id が 4 の場合だけ 404 エラーを返す } $seconds = pow($id, 2); // $id を2乗して秒数にする sleep($seconds); } }
この中でやっているのは次のとおりです。
request()
この中でやっている手順は次のとおりです。
- URLをつくる
- Promise(非同期通信のためのアクセス情報)をつくり、配列へ貯めていく
- 貯まった Promise を settle() へセットし、非同期通信を開始
- アクセスが全て終わったら完了
なお、今回はコードが短いので、アクセスに失敗した場合の処理もつけています。実際には、$i
が4
のときです。
つまり、then()
には1つ目がアクセスに成功した時で、2つ目は失敗したときの処理を書くことになります。
response()
このメソッドは非同期通信のアクセス先で、受け取ったIDを2乗した秒数を待機することになります。
つまり、
- $i が 1 の場合: 1 秒待つ
- $i が 2 の場合: 4 秒待つ
- $i が 3 の場合: 9 秒待つ
ことになります。
また、$i
が4
のときの処理は先ほど説明したとおりです。
ルートをつくる
では、先ほどのコントローラーをルートにセットしましょう。
routes/web.php
// 省略 use App\Http\Controllers\AsyncController; // 省略 Route::get('async/request', [AsyncController::class, 'request'])->name('async.request'); Route::get('async/response/{id}', [AsyncController::class, 'response'])->name('async.response');
作業はたったこれだけで完了です。(Guzzle
様々ですね👍)
お疲れ様でした😄✨
ちなみに: AWSのパッケージには非同期型が用意されています
AWS
が用意してくれているaws/aws-sdk-php
というPHP用パッケージには、いろいろなクラスがありますが、その中にはGuzzleの非同期通信に対応しているものもあります。
例えば、Amazon Rekognition
の場合、以下のようにdetectCustomLabelsAsync()
のようなメソッドを使うとPromise
を取得することができます。
$client = new RekognitionClient([ 'region' => 'ap-northeast-1', 'version' => 'latest' ]); $promises[] = $client->detectCustomLabelsAsync([ 'Image' => [ 'Bytes' => $bytes, ], 'ProjectVersionArn' => env('AWS_REKOGNITION_ARN') ]);
つまり、わざわざ自前でURLとパラメータを用意しなくてもいいというわけですね。さすがAWS
です👍
テストしてみる
では実際にテストしてみましょう❗
ブラウザで「https://*****/async/request」にアクセスします。
すると・・・・・・
少しわかりにくですが、まとめると次のようになります。
【各URLが完了した時間】
- 1つ目のURL: 開始から 約 1.09 秒で完了
- 2つ目のURL: 開始から 約 4.08 秒で完了
- 3つ目のURL: 開始から 約 9.95 秒で完了
【全てが完了した時間】
約9.953
秒で完了
つまり、ひとつずつ「アクセス&待つ」で実行するとしたら最低でも開始から15
秒はかかる計算になりますが、非同期通信を使ったおかげで9.953
秒で完了したということになります。
高速化、成功です ✨😄👍
企業様へのご提案
今回の非同期通信を使うと、AWS
やGoogle Cloud
などのクラウドへのアクセスが重複する場合でも最低限の時間で完了することができるようになります。
また、実行環境の影響でどうしても外部サーバーからデータを取得する必要がある場合や、いくつかの環境に処理を分散する場合にも有効です。
もしそういった高速化を実装されたい場合は、ぜひお問い合わせからご連絡ください。お待ちしております。😄✨
おわりに
ということで、今回はLaravel + Guzzle
で非同期通信を実装してみました。
「非同期通信」という言葉がちょっとこむつかしい響きですが、Guzzle
を使えばとてもシンプルに実装できることが分かっていただけたと思います。
ちなみに、Guzzle
はとてもよくできたパッケージなのでファイルのアップロードや、ヘッダーのセットなどもシンプルに実装することができます。
昔のことを考えると、開発者に感謝の気持ちで一杯ですね。
まさに「You saved my day!」です。
できたら、私のコードも世のためになれば嬉しいです。
ではでは〜❗
「ランチェスター戦略の本買いました。
オリラジあっちゃんの影響で😂」