【问题标题】:Guzzle async requests not really async?Guzzle 异步请求不是真正的异步?
【发布时间】:2018-02-11 10:06:46
【问题描述】:

问题

我们正在尝试使用 guzzle 进行并发异步请求。在浏览了一些资源后,比如thisthis,我们想出了一些代码,在下面分享。但是它没有按预期工作。

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

期待

仅出于测试目的,我们点击了一个内部 url,它会休眠 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();

实际结果

我们发现 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 

操作系统/版本信息

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

问题可能出在 \GuzzleHttp\Promise\each_limit .. 这可能没有足够快地启动或解决承诺。我们可能不得不在外部将其欺骗为ticking。

【问题讨论】:

    标签: php guzzle6


    【解决方案1】:

    在示例代码中,您将为每个要发出的请求创建一个新的GuzzleHttp\Client 实例。这似乎并不重要,但是,在GuzzleHttp\Client 的实例化过程中,如果没有提供,它将设置默认的handler。 (这个值随后会传递给通过客户端发送的任何请求,除非它被覆盖。)

    注意:它从this 函数中确定要使用的最佳处理程序。不过,它很可能最终会默认为curl_mutli_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();
    

    【讨论】:

    • 创建一个新客户端很重要的原因是因为需求决定了代理服务器和其他请求特定参数的使用。这意味着,每个请求都可能是完全不同的请求,包括 url、header 和 method。如果我创建一个客户端实例,我不知道它将与其他请求共享什么。按照这种逻辑,您提出的第二种解决方案可能更可取。我将尝试上面提出的两种解决方案并报告。太糟糕了,我现在在家.. 并且没有设置来尝试上述操作。
    • 您应该可以使用proxy 选项docs.guzzlephp.org/en/stable/request-options.html#proxy 根据您的请求进行设置。
    • 通过每次创建一个新的处理程序,您的所有请求都没有被正确分组 为什么这是个问题? 为什么他们不能在不同的组中一起运行?这是否意味着如果我只发出一个请求,它将始终同步运行?
    • 我来这里是为了发现为了运行异步,php-curl 是必须的。我没有安装它,我花了 2 天时间试图找到问题。
    • @AdamLavin 在 master 分支中提供指向特定文件(甚至行!)的链接不是一个好主意 - 几乎可以肯定它们会随着时间的推移而过时。更好的选择是使用修订哈希而不是分支名称
    猜你喜欢
    • 1970-01-01
    • 2016-10-06
    • 2022-11-28
    • 2017-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-19
    相关资源
    最近更新 更多