yasutomogのブログ

Software Engineerの雑記

PHPでGuzzleHttpを使用して並列処理(同時実行数の制御)の確認

概要

  • PHPでHTTP通信の並列処理をする
  • curl_multiではなくGuzzleHttpを使って試す
  • GuzzleHttpは並列実行が元々用意されている

知りたかったこと

  • GuzzleHttpでPoolの使用時に設定するconcurrency(並列処理数)の制御
  • 具体的には、全部で10リクエスト投げる処理でconcurrencyに2を設定して実行した場合、以下のどちらになるか調査
    • 片一方のリクエストが先に返ってきても、もう片方のリクエストが返るまで待ち、常に2リクエストずつ処理する
    • レスポンスが返ってきて空いたところから次のリクエストを投げて常時2リクエストを保とうとする

試した環境

  • lumen: v5.5.2
  • GuzzleHttp: 6.3.3(lumen導入時に一緒に入ったもの)

試したサンプルコード

routes/web.php

  • リクエストパラメータの秒数分Sleep後にレスポンスを返す関数とURLのマッピング
$router->get('/wait', 'SampleController@wait');

app/Http/Controllers/SampleController.php

  • リクエストパラメータの秒数分Sleep後にレスポンスを返す関数
public function wait(Request $request)
{

    $wait = $request->input("sec");
    sleep((int)$wait);
    return "ok" . $wait;

}

app/Console/Kernel.phpの$commandsに追加

  • API呼び出しをartisanを呼び出すため、実行クラスを追加
    protected $commands = [
        \App\Console\Commands\Sample\ApiCaller::class,
    ];

app/Console/Commands/Sample/ApiCaller.php

  • APIを呼び出す実行クラス
  • 全部で10リクエスト呼び出す
  • 10リクエストの内、2つめ(indexが1)のリクエストのみ10秒でそれ以外は1秒で返されるようにしている
<?php

namespace App\Console\Commands\Sample;

use Illuminate\Console\Command;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;

class ApiCaller extends Command
{

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'apicall';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'API呼び出し処理';

    /**
     * API呼び出し処理
     */
    public function handle()
    {

        $this->multiReq();

    }

    private function multiReq()
    {

        $uri = [
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=10',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
            'http://localhost/api/wait?sec=1',
        ];

        $client = new Client();
        $requests = function () use ($uri) {
            foreach ($uri as $u) {
                yield new Request('GET', $u);
            }
        };

        $pool = new Pool($client, $requests(), [
            'concurrency' => 2,
            'fulfilled' => function ($response, $index) {
                // this is delivered each successful response
                \Log::info("成功::index::" . $index);
            },
            'rejected' => function ($reason, $index) {
                // this is delivered each failed request
                \Log::info("失敗::index::" . $index);
            },
        ]);

        // Initiate the transfers and create a promise
        $promise = $pool->promise();

        // Force the pool of requests to complete.
        $promise->wait();

    }

}

実行コマンド

php artisan apicall

結果

  • 以下のようなログが出力される
  • 2つ目(indexが1)の10秒掛かるリクエストが返されるまでに3つ目のリクエストが投げられなか確認したところ、レスポンス待たずにどんどんリクエストしているので並列処理としては常時2つを保つように動いていることがわかった
[2018-12-05 17:17:34] lumen.INFO: 成功::index::0 [] []
[2018-12-05 17:17:36] lumen.INFO: 成功::index::2 [] []
[2018-12-05 17:17:38] lumen.INFO: 成功::index::3 [] []
[2018-12-05 17:17:40] lumen.INFO: 成功::index::4 [] []
[2018-12-05 17:17:42] lumen.INFO: 成功::index::5 [] []
[2018-12-05 17:17:43] lumen.INFO: 成功::index::1 [] []
[2018-12-05 17:17:44] lumen.INFO: 成功::index::7 [] []
[2018-12-05 17:17:44] lumen.INFO: 成功::index::6 [] []
[2018-12-05 17:17:45] lumen.INFO: 成功::index::8 [] []
[2018-12-05 17:17:45] lumen.INFO: 成功::index::9 [] []