如何同时执行多个Guzzle请求?

32

我可以使用 Guzzle 执行单个请求,并且迄今为止非常满意 Guzzle 的性能,但是,在 Guzzle API 中,我看到了一些关于 MultiCurl 和批处理的内容。

有人可以向我解释如何同时发出多个请求吗?如果可能的话,异步进行。我不知道这是否是 MultiCurl 的意思。同步也不是问题。我只想在同一时间或者非常短的时间内执行多个请求。


文档中有一个演示。从您的角度来看,这仍然是同步调用,但在内部将是并行的 - 因此调用的总时间只是单个最长获取的时间。 - halfer
3个回答

30

在 Guzzle 中,我们可以向同一 URL 发送不同的数据吗? - Habib Rehman

29

关于新的 GuzzleHttp guzzlehttp/guzzle 的更新

并发/并行调用现在使用几种不同的方法,包括 Promise.. 并发请求

旧的传递 RequestInterfaces 数组的方式将不再起作用。

查看此处的示例

    $newClient = new  \GuzzleHttp\Client(['base_uri' => $base]);
    foreach($documents->documents as $doc){

        $params = [
            'language' =>'eng',
            'text' => $doc->summary,
            'apikey' => $key
        ];

        $requestArr[$doc->reference] = $newClient->getAsync( '/1/api/sync/analyze/v1?' . http_build_query( $params) );
    }

    $time_start = microtime(true);
    $responses = \GuzzleHttp\Promise\unwrap($requestArr); //$newClient->send( $requestArr );
    $time_end = microtime(true);
    $this->get('logger')->error(' NewsPerf Dev: took ' . ($time_end - $time_start) );
更新: 如评论所建议并由@sankalp-tambe提出的要求,您也可以使用不同的方法来避免一组失败的并发请求不返回所有响应。
虽然使用Pool建议的选项是可行的,但我仍然更喜欢Promise。
一个使用Promise的例子是使用settle和wait方法而不是unwrap。
与上面示例的区别在于:
$responses = \GuzzleHttp\Promise\settle($requestArr)->wait(); 

我已创建了下面的完整示例,供参考如何处理 $responses。

require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Promise as GuzzlePromise;

$client = new GuzzleClient(['timeout' => 12.0]); // see how i set a timeout
$requestPromises = [];
$sitesArray = SiteEntity->getAll(); // returns an array with objects that contain a domain

foreach ($sitesArray as $site) {
    $requestPromises[$site->getDomain()] = $client->getAsync('http://' . $site->getDomain());
}

$results = GuzzlePromise\settle($requestPromises)->wait();

foreach ($results as $domain => $result) {
    $site = $sitesArray[$domain];
    $this->logger->info('Crawler FetchHomePages: domain check ' . $domain);

    if ($result['state'] === 'fulfilled') {
        $response = $result['value'];
        if ($response->getStatusCode() == 200) {
            $site->setHtml($response->getBody());
        } else {
            $site->setHtml($response->getStatusCode());
        }
    } else if ($result['state'] === 'rejected') { 
        // notice that if call fails guzzle returns is as state rejected with a reason.

        $site->setHtml('ERR: ' . $result['reason']);
    } else {
        $site->setHtml('ERR: unknown exception ');
        $this->logger->err('Crawler FetchHomePages: unknown fetch fail domain: ' . $domain);
    }

    $this->entityManager->persist($site); // this is a call to Doctrines entity manager
}

这个示例代码最初是在这里发布的。


虽然这对于我的用例——一次加载多个图像URL数据非常有效,但如果正在加载的URL之一抛出404错误,该怎么处理呢?当发生这种情况时,Guzzle会出现异常并抛出一个guzzle异常。我无法保证URL的可用性,因此希望只需加载多个请求并使用实际通过的请求。 - georaldc
1
没关系,我刚刚重新编写了我的代码,使用了 GuzzleHttp\Pool。它似乎也很好用,并且给了我更多的控制权。 - georaldc
3
可以分享一下使用池子(Pool)处理多个URL的代码吗? - Sankalp Tambe
@georaldc,我不是很喜欢池化方法。Sankalp,你可以在我的答案更新中看到一个完整的替代方案,它将返回所有响应而不是异常。 - Bizmate
@Bizmate 谢谢,这对我有用。我很好奇并发级别和它比旧的异步方式更高效多少? - awm

12

Guzzle 6.0让发送多个异步请求变得非常容易。

有多种方法可以实现。

您可以创建异步请求并将结果的 promises 添加到单个数组中,然后使用 settle() 方法获取结果,如下所示:

$promise1 = $client->getAsync('http://www.example.com/foo1');
$promise2 = $client->getAsync('http://www.example.com/foo2');
$promises = [$promise1, $promise2];

$results = GuzzleHttp\Promise\settle($promises)->wait();

现在,您可以循环遍历这些结果并使用GuzzleHttpPromiseallGuzzleHttpPromiseeach来获取响应。有关更多详细信息,请参阅此文章

如果您有一个不确定数量的请求需要发送(例如,这里有5个),则可以使用GuzzleHttp/Pool::batch()。以下是一个示例:

$client = new Client();

// Create the requests
$requests = function ($total) use($client) {
    for ($i = 1; $i <= $total; $i++) {
        yield new Request('GET', 'http://www.example.com/foo' . $i);
    }
};

// Use the Pool::batch()
$pool_batch = Pool::batch($client, $requests(5));
foreach ($pool_batch as $pool => $res) {

    if ($res instanceof RequestException) {
        // Do sth
        continue;
    }

    // Do sth
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接