Guzzle异步请求不是真的异步?

Sca*_*ble 13 php guzzle6

问题

我们正在尝试使用guzzle进行并发异步请求.通过资源很少去,等之后这个这个,我们想出了如下共享一些代码.但是它没有按预期工作.

看起来Guzzle正在同步执行这些请求而不是异步.

期望

仅仅是出于测试目的,我们正在打一个内部网址,它会进行5秒的睡眠.在并发性为10的情况下,我们预计所有10个请求最初将排队并几乎同时发送到服务器,在那里它们将等待5秒,然后几乎所有这些请求几乎将在同一时间完成.这将使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();
Run Code Online (Sandbox Code Playgroud)

实际结果

我们发现Guzzle从未提出第二个请求,直到第一个请求完成.等等.

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 
Run Code Online (Sandbox Code Playgroud)

操作系统/版本信息

  • Ubuntu的
  • PHP/7.1.3
  • GuzzleHttp/6.2.1
  • 卷曲/ 7.47.0

问题可能是\ GuzzleHttp\Promise\each_limit ..这可能不会足够快地启动或解决承诺.我们有可能必须将其欺骗到tick外部.

小智 11

在示例代码中,您要为要生成的GuzzleHttp\Client每个请求创建一个新实例.这可能看起来不重要,但是,在实例化期间,如果没有提供GuzzleHttp\Client默认处理程序,它将设置默认处理程序.(此值随后传递给通过客户端发送的任何请求,除非被覆盖.)

注:确定从用最好的处理这个功能.虽然,它最有可能最终违约curl_mutli_exec.

这有什么重要意义?它是底层处理程序,负责同时跟踪和执行多个请求.通过每次创建一个新的处理程序,您的所有请求都无法正确分组并一起运行.有关这方面的更多信息,请参阅curl_multi_exec文档.

所以,你有两种处理方式:

通过客户端传递给迭代器:

$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();
Run Code Online (Sandbox Code Playgroud)

或者在别处创建处理程序并将其传递给客户端:(虽然我不确定你为什么这样做,但它就在那里!)

$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();
Run Code Online (Sandbox Code Playgroud)

  • 有用.谢谢你的解决方案. (3认同)
  • *通过每次创建一个新的处理程序,您的请求都没有被正确分组* **为什么这是一个问题?** 为什么它们不能在单独的组中一起运行?这是否意味着如果我只发出一个请求,它将始终同步运行? (2认同)
  • 我来这里是为了运行异步,php-curl是必须的。我没有安装它,我花了2天的时间来查找问题。 (2认同)
  • @AdamLavin 在 master 分支中提供特定文件(甚至行!)的链接不是一个好主意 - 几乎可以肯定,随着时间的推移它们会变得过时。更好的选择是使用修订哈希而不是分支名称 (2认同)