Жаловать запросы async не очень асинхронно?

проблема

Мы пытаемся выполнять одновременные асинхронные запросы с использованием жужжания. Пройдя несколько ресурсов, вроде этого и этого, мы придумали некоторый код, который будет использоваться ниже. Однако он работает не так, как ожидалось.

Похоже, что Guzzle выполняет эти запросы синхронно, а не async.

ожидание

Только для тестовых целей мы нажимаем внутренний URL-адрес, который делает 5-секундный сон. При параллелизме 10 мы ожидаем, что все 10 запросов будут сначала поставлены в очередь и будут отправляться на сервер почти одновременно, где они будут ждать 5 секунд, и тогда почти все из них будут завершены почти одновременно. Это заставит клиента жужжать получить 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();

Фактические результаты

Мы находим, что Гузл никогда не делал второй запрос до тех пор, пока первый не будет завершен. и так далее.

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 

Информация о ОС/версии

  • Ubuntu
  • PHP/7.1.3
  • GuzzleHttp/6.2.1
  • локон /7.47.0

Проблема может быть в \GuzzleHttp\Promise\each_limit.., которая, возможно, не инициирует или не разрешает обещание достаточно быстро. Вполне возможно, что мы должны обмануть, что в tick ИНГ извне.

Ответ 1

В примере кода вы создаете новый GuzzleHttp\Client для каждого запроса, который хотите сделать. Это может показаться GuzzleHttp\Client, однако, во время создания GuzzleHttp\Client он будет устанавливать обработчик по умолчанию, если он не GuzzleHttp\Client. (Это значение затем передается на любой запрос, отправляемый через Клиент, если только он не переопределяется.)

Примечание. Он определяет лучший обработчик для использования с этой функцией. Хотя, скорее всего, это приведет к curl_mutli_exec.

Какая важность этого? Это базовый обработчик, который отвечает за отслеживание и выполнение нескольких запросов одновременно. Каждый раз создавая новый обработчик, ни один из ваших запросов не группируется и не работает вместе. Для более глубокого понимания этого возьмите curl_multi_exec в curl_multi_exec docs.

Итак, у вас есть два способа справиться с этим:

Пройдите через клиента через итератор:

$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();