目录

Guzzlehttp并发正确使用姿势

最近在对老旧的PHP服务做性能调优,使用了一个架构组基于Guzzle写的第三方调用组件,虽然看起来写法是并发的,但是服务整体的耗时佐证了是串行的。在前年的时候自己也在公司内部写了一个基于Guzzle的第三方调用组件,里面集成了将多个服务并行发起请求的能力,但是最近在一个业务方实际使用的时候,发现其实也是串行的。

原始的写法

最开始是从所有的添加requestservice中遍历出所有的req后一起放到Promise\settle中,这种方式看似是PHP意义上的并发,具体的示例如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    public static function send()
    {
        ......
        $promises = [];
        foreach (self::$clientList as $httpClient) {
            $client_promises = $httpClient->getAllRequestPromises();
            foreach ($client_promises as  $item) {
                $promises[] = $item;
            }
        }
        $results   = [];
        $responses = Promise\settle($promises)->wait();
        ......
    }

这种请求,看起来每个$promise间是独立的,其实对于Guzzle来说底层来说只有一个client内的promise才会并发处理,我在对Guzzle底层进行了多级debug后,验证了这个逻辑。其实这儿也可以去看Guzzle的官方示例提供的用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use GuzzleHttp\Client;
use GuzzleHttp\Promise;

$client = new Client(['base_uri' => 'http://httpbin.org/']);

// Initiate each request but do not block
$promises = [
    'image' => $client->getAsync('/image'),
    'png'   => $client->getAsync('/image/png'),
    'jpeg'  => $client->getAsync('/image/jpeg'),
    'webp'  => $client->getAsync('/image/webp')
];

// Wait for the requests to complete; throws a ConnectException
// if any of the requests fail
$responses = Promise\Utils::unwrap($promises);

// You can access each response using the key of the promise
echo $responses['image']->getHeader('Content-Length')[0];
echo $responses['png']->getHeader('Content-Length')[0];

// Wait for the requests to complete, even if some of them fail
$responses = Promise\Utils::settle($promises)->wait();

// Values returned above are wrapped in an array with 2 keys: "state" (either fulfilled or rejected) and "value" (contains the response)
echo $responses['image']['state']; // returns "fulfilled"
echo $responses['image']['value']->getHeader('Content-Length')[0];
echo $responses['png']['value']->getHeader('Content-Length')[0];

新的写法

在确认了只有这种写法才能并发后,就需要对原先的写法做改造了,整体的思路和官方处理保持一致,在真正send的时候重新实例化一个client,将获取到的promise内的请求参数重新放到client中,具体的改造如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    public static function send()
    {
        ......
        $client = new Client();
        $promises = [];
        foreach (self::$clientList as $httpClient) {
            $client_promises = $httpClient->getAllRequestPromises();
            foreach ($client_promises as  $item) {
                $promises[] = $client->requestAsync($item['method'], rtrim($item['bese_uri'], '/') . '/' . ltrim($item['uri'], '/'), $item['options']);
            }
        }
        $results   = [];
        $responses = Promise\settle($promises)->wait();
        ......
    }

效果

接口的P95下降降300ms,avg下降80ms,大家在使用的时候还是要多看官方文档,多实践,毕竟实践是检验真理的唯一标准