如何在PHP中做並發請求
原生的PHP語言是單進程模式,一個請求對應一個進程,I/O是同步阻塞的,如果想要做到並發多進程的方式,就需要仰賴第三方套件,這次使用的套件是 guzzlehttp/guzzle ,這是一個PHP中很常使用做跨域請求的套件
安裝方式很簡單,只要使用 Composer 就可以了
composer require guzzlehttp/guzzle:^7.0
以下就寫個範例,測試一下如何做並發測試
可以寫個console command做並發請求發請源
$client = new Client();
// Initiate each request but do not block
$promises = [
'sleep_1' => $client->getAsync('http://localhost/sleep_1'),
'sleep_2' => $client->getAsync('http://localhost/sleep_2'),
'sleep_3' => $client->getAsync('http://localhost/sleep_3'),
'sleep_4' => $client->getAsync('http://localhost/sleep_4')
];
// Wait for the requests to complete; throws a ConnectException
// if any of the requests fail
$responses = Promise\unwrap($promises);
foreach ($responses as $response) {
dump($response->getBody()->getContents());
}
然後再另一端開啟服務做request的接收,這邊寫四個路由,分別sleep 1~4 秒,在回傳結果
// 路由1,暫停1秒
Route::get('sleep_1', function () {
// throw new \Exception('error');
$start = now();
sleep(1);
return 'sleep_1 start at ' . $start . ' / sleep_1_done at ' . now();
});// 路由2,暫停2秒
Route::get('sleep_2', function () {
$start = now();
sleep(2);
return 'sleep_2 start at ' . $start . ' / sleep_2_done at ' . now();
});// 路由3,暫停3秒
Route::get('sleep_3', function () {
$start = now();
sleep(3);
return 'sleep_3 start at ' . $start . ' / sleep_3_done at ' . now();
});// 路由4,暫停4秒
Route::get('sleep_4', function () {
$start = now();
sleep(4);
return 'sleep_4 start at ' . $start . ' / sleep_4_done at ' . now();
});
可以看到執行結果

整個流程只有跑 4 秒,證明四個請求是並發,I/O 非阻塞的
接下來會做一些遇到例外的展示
假設有其中一個請求失敗了
Route::get('sleep_2', function () {
// 拋列外,假設該請求失敗了
throw new \Exception('error');
$start = now();
sleep(2);
return 'sleep_2 start at ' . $start . ' / sleep_2_done at ' . now();
});
會發現 接收端會報錯誤

這時候 接收端就需要 做些修改 改成使用
Promise\settle($promises)->wait();
來接收 responses
// Wait for the requests to complete, even if some of them fail
$responses = Promise\settle($promises)->wait();
foreach ($responses as $key => $response) {
dump($key . ' 回傳狀態: ' . $response['state']);
}
這種方式 就算有請求某個錯誤也不會導致整個並發請求中止

當請求成功時,狀態是fulfilled,失敗時,狀態是rejected
這樣就可以只接收成功的回傳訊息就可以了
// Wait for the requests to complete, even if some of them fail
$responses = Promise\settle($promises)->wait();
foreach ($responses as $key => $response) {
$state = Arr::get($response, 'state');
if ($state === 'fulfilled') {
$value = Arr::get($response, 'value');
dump($value->getBody()->getContents());
}
}
