順序串行請求多個 API 時,總耗時 = 所有請求耗時之和。並發請求可將總耗時降為 **最慢單個請求的耗時**,對聚合多個微服務、爬蟲、批量推送等場景至關重要。
curl_multi_init() 創建批處理句柄curl_multi_add_handle() 添加多個單獨 curl handlecurl_multi_exec() 循環執行,直到無運行中句柄curl_multi_getcontent() 獲取各句柄響應curl_multi_remove_handle() 清理<?php
class Http_MultiRequest {
private $urls = [];
private $options = [];
public function __construct(array $options = []) {
$this->setOptions($options);
}
public function setUrls(array $urls) {
$this->urls = $urls;
return $this;
}
public function setOptions(array $options) {
// 默認選項
$defaults = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HEADER => false,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; HttpMultiRequest/1.0)',
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false, // 僅測試環境,生產請配置 CA
CURLOPT_SSL_VERIFYHOST => false,
];
// POST 支持
if (isset($options['HTTP_POST'])) {
$defaults[CURLOPT_POST] = true;
$defaults[CURLOPT_POSTFIELDS] = $options['HTTP_POST'];
unset($options['HTTP_POST']);
}
$this->options = $defaults + $options; // 用戶選項覆蓋默認
}
// 並發執行所有 URL
public function exec() {
if (empty($this->urls)) return false;
$mh = curl_multi_init();
$handles = [];
foreach ($this->urls as $k => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, $this->options);
curl_multi_add_handle($mh, $ch);
$handles[$k] = $ch;
}
// 執行循環 (標準寫法)
$running = null;
do {
curl_multi_exec($mh, $running);
// 關鍵:curl_multi_select 等待活動,避免 CPU 100% 空轉
if ($running) curl_multi_select($mh, 0.1);
} while ($running > 0);
// 收集結果
$results = [];
foreach ($handles as $k => $ch) {
$results[$k] = curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
return $results;
}
// 單個請求 (串行)
public function execOne($url) {
$ch = curl_init($url);
curl_setopt_array($ch, $this->options);
$content = curl_exec($ch);
curl_close($ch);
return $content;
}
}
?>
原文中的 do { curl_multi_exec(...) } while ($running); **會導致 CPU 100% 空轉**,因為 curl_multi_exec 非阻塞立即返回。必須加入 curl_multi_select($mh, $timeout) 讓進程睡眠等待 I/O 事件,這是官方文檔強調的正確用法。
<?php
$urls = array_fill(0, 8, 'https://httpbin.org/delay/1'); // 8個 1秒延遲接口
$client = new Http_MultiRequest();
// 並發
$start = microtime(true);
$resParallel = $client->setUrls($urls)->exec();
$tParallel = microtime(true) - $start;
// 串行
$start = microtime(true);
$resSerial = [];
foreach ($urls as $url) $resSerial[] = $client->execOne($url);
$tSerial = microtime(true) - $start;
echo "並發耗時: {$tParallel:.2f}s (理論 ~1s)\n";
echo "串行耗時: {$tSerial:.2f}s (理論 ~8s)\n";
echo "加速比: " . ($tSerial / $tParallel) . "x\n";
?>
典型輸出:
並發耗時: 1.15s 串行耗時: 8.32s 加速比: 7.2x
curl_errno($ch)、curl_getinfo($ch, CURLINFO_HTTP_CODE),區分網絡錯誤、HTTP 錯誤、超時。CURLOPT_TIMEOUT (總超時) 和 CURLOPT_CONNECTTIMEOUT (連接超時),防止單個慢請求阻塞整個批次。