web-dev-qa-db-ja.com

ガズル非同期リクエストは本当に非同期ではありませんか?

問題

Guzzleを使用して同時非同期リクエストを実行しようとしています。 thisthis のようないくつかのリソースを調べた後、以下で共有するコードを思いつきました。ただし、期待どおりに機能していません。

Guzzleはこれらのリクエストを非同期ではなく同期的に実行しているようです。

期待

テスト目的のためだけに、内部URLにアクセスして、5秒間スリープします。同時実行数が10の場合、10個の要求すべてが最初にキューに入れられ、サーバーにalmost同時に送信され、そこで5秒間待機します。その後、almostそれらのすべてがほぼ同時に終了します。これにより、guzzleクライアントはイテレーターなどから10個の新しいリクエストを取得します。

コード

    $iterator = function() {
        $index = 0;
        while (true) {
            $client = new Client(['timeout'=>20]);
            $url = 'http://localhost/wait/5' . $index++;
            $request = new Request('GET',$url, []);
            echo "Queuing $url @ " . (new Carbon())->format('Y-m-d H:i:s') . PHP_EOL;
            yield $client
                ->sendAsync($request)
                ->then(function(Response $response) use ($request) {
                    return [$request, $response];
                });
        }
    };

    $promise = \GuzzleHttp\Promise\each_limit(
        $iterator(),
        10,  /// concurrency,
        function($result, $index) {
            /** GuzzleHttp\Psr7\Request $request */
            list($request, $response) = $result;
            echo (string) $request->getUri() . ' completed '.PHP_EOL;
        },
        function(RequestException $reason, $index) {
            // left empty for brevity
        }
    );
    $promise->wait();

実績

最初のリクエストが終了するまで、Guzzleが2回目のリクエストを行うことはありませんでした。等々。

Queuing http://localhost/wait/5/1 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/2 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/3 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/4 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/5 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/6 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/7 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/8 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/9 @ 2017-09-01 17:15:28
Queuing http://localhost/wait/5/10 @ 2017-09-01 17:15:28
http://localhost/wait/5/1 completed
Queuing http://localhost/wait/5/11 @ 2017-09-01 17:15:34
http://localhost/wait/5/2 completed
Queuing http://localhost/wait/5/12 @ 2017-09-01 17:15:39
http://localhost/wait/5/3 completed
Queuing http://localhost/wait/5/13 @ 2017-09-01 17:15:45
http://localhost/wait/5/4 completed
Queuing http://localhost/wait/5/14 @ 2017-09-01 17:15:50 

OS /バージョン情報

  • Ubuntu
  • PHP/7.1.3
  • GuzzleHttp/6.2.1
  • curl/7.47.0

この問題は、\ GuzzleHttp\Promise\each_limit ..で発生する可能性があります。それをだまして外部でtickingにする必要があるかもしれません。

11
Scalable

サンプルコードでは、作成するすべてのリクエストに対して新しいGuzzleHttp\Clientインスタンスを作成しています。これは重要ではないように思われるかもしれませんが、GuzzleHttp\Clientのインスタンス化中に、デフォルトが設定されます handler 何も提供されない場合。 (この値は、オーバーライドされない限り、クライアントを介して送信されるリクエストに渡されます。)

注: this 関数から使用する最適なハンドラーを決定します。ただし、ほとんどの場合、デフォルトはcurl_mutli_execになります。

これの重要性は何ですか?複数のリクエストを同時に追跡および実行するのは、基になるハンドラーです。毎回新しいハンドラーを作成することにより、リクエストが適切にグループ化され、一緒に実行されません。これについてもう少し洞察を得るには、 curl_multi_exec docs をご覧ください。

そのため、これに対処するには2つの方法があります。

クライアントをイテレーターにパススルーします。

$client = new GuzzleHttp\Client(['timeout' => 20]);

$iterator = function () use ($client) {
    $index = 0;
    while (true) {
        if ($index === 10) {
            break;
        }

        $url = 'http://localhost/wait/5/' . $index++;
        $request = new Request('GET', $url, []);

        echo "Queuing $url @ " . (new Carbon())->format('Y-m-d H:i:s') . PHP_EOL;

        yield $client
            ->sendAsync($request)
            ->then(function (Response $response) use ($request) {
                return [$request, $response];
            });

    }
};

$promise = \GuzzleHttp\Promise\each_limit(
    $iterator(),
    10,  /// concurrency,
    function ($result, $index) {
        /** @var GuzzleHttp\Psr7\Request $request */
        list($request, $response) = $result;
        echo (string)$request->getUri() . ' completed ' . PHP_EOL;
    }
);
$promise->wait();

または、ハンドラーを別の場所に作成してクライアントに渡します(なぜこれを行うのかはわかりませんが、あります!)

$handler = \GuzzleHttp\HandlerStack::create();

$iterator = function () use ($handler) {
    $index = 0;
    while (true) {
        if ($index === 10) {
            break;
        }

        $client = new Client(['timeout' => 20, 'handler' => $handler])
        $url = 'http://localhost/wait/5/' . $index++;
        $request = new Request('GET', $url, []);

        echo "Queuing $url @ " . (new Carbon())->format('Y-m-d H:i:s') . PHP_EOL;

        yield $client
            ->sendAsync($request)
            ->then(function (Response $response) use ($request) {
                return [$request, $response];
            });

    }
};

$promise = \GuzzleHttp\Promise\each_limit(
    $iterator(),
    10,  /// concurrency,
    function ($result, $index) {
        /** @var GuzzleHttp\Psr7\Request $request */
        list($request, $response) = $result;
        echo (string)$request->getUri() . ' completed ' . PHP_EOL;
    }
);
$promise->wait();
11
Adam Lavin