首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP cURL 多執行緒併發請求實戰:從入門到高效能優化

     編輯:PHP 綜合知識

為什麼需要 cURL Multi?

傳統 curl_exec 是同步阻塞的:逐個請求、等待回應、再發下一個。若需抓取 100 個 API、每個 200ms,串行需 20 秒。curl_multi_* 系列函數實現單進程非阻塞並發,可在數秒內完成同等工作量,大幅提升爬蟲、聚合 API、批次推播等場景效率。

核心流程六步驟

  1. curl_multi_init():建立多執行緒控制代碼
  2. 迴圈 curl_init() → curl_setopt() → curl_multi_add_handle():加入子請求
  3. curl_multi_exec() 迴圈執行:非阻塞驅動所有請求
  4. curl_multi_getcontent() 取得各請求回應
  5. curl_multi_remove_handle() + curl_close() 清理子控制代碼
  6. curl_multi_close() 釋放主控制代碼

基本範例(含 CPU 100% 坑點)

<?php
$urls = [
    'https://www.cnn.com/',
    'https://www.canada.com/',
    'https://www.yahoo.com/'
];

$mh = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 單請求超時
    $handles[$i] = $ch;
    curl_multi_add_handle($mh, $ch);
}

// ❌ 糟糕寫法:忙等待導致 CPU 100%
do {
    $mrc = curl_multi_exec($mh, $active);
} while ($active);

$results = [];
foreach ($handles as $i => $ch) {
    $results[$i] = curl_multi_getcontent($ch);
    curl_multi_remove_handle($mh, $ch);
    curl_close($ch);
}
curl_multi_close($mh);
print_r($results);

優化版:使用 curl_multi_select 避免忙等待

<?php
$mh = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MyBot/1.0)');
    $handles[$i] = $ch;
    curl_multi_add_handle($mh, $ch);
}

$active = null;
// 執行主迴圈
do {
    // 1. 盡可能執行所有可立即處理的動作
    $mrc = curl_multi_exec($mh, $active);
    while ($mrc === CURLM_CALL_MULTI_PERFORM) {
        $mrc = curl_multi_exec($mh, $active);
    }

    // 2. 若仍有進行中請求,等待活動發生(阻塞不佔 CPU)
    if ($active && $mrc === CURLM_OK) {
        if (curl_multi_select($mh) === -1) {
            // select 失敗或無檔案描述符,短暫睡眠避免忙等
            usleep(10000); // 10ms
        }
    }
} while ($active && $mrc === CURLM_OK);

// 收集結果
$results = [];
foreach ($handles as $i => $ch) {
    $errno = curl_errno($ch);
    $error = curl_error($ch);
    $results[$i] = [
        'content' => $errno ? null : curl_multi_getcontent($ch),
        'error'   => $errno ? "cURL error ($errno): $error" : null,
        'info'    => curl_getinfo($ch),
    ];
    curl_multi_remove_handle($mh, $ch);
    curl_close($ch);
}
curl_multi_close($mh);
print_r($results);

關鍵優化細節

優化點說明設定範例
連線超時避免卡在 DNS/TCP 握手CURLOPT_CONNECTTIMEOUT => 5
總超時單請求最長執行時間CURLOPT_TIMEOUT => 30
User-Agent避免被反爬蟲封禁CURLOPT_USERAGENT => '...'
Follow Location自動處理 3xx 重導向CURLOPT_FOLLOWLOCATION => true
SSL 驗證生產環境務必驗證CURLOPT_SSL_VERIFYPEER => true
限制併發數避免耗盡檔案描述符/記憶體分批處理,每批 50-100 個
錯誤處理區分網路錯誤、HTTP 錯誤、超時curl_errno()、curl_getinfo()['http_code']

實戰壓測經驗分享

作者實測對 Memcached 讀寫介面進行併發模擬:

  • 10 執行緒 × 1000 請求:穩定
  • 100 執行緒 × 1000 請求:穩定
  • 1000 執行緒 × 100 請求:系統負載極高,不建議超過 1000 併發

結論:PHP cURL Multi 適合中等併發(< 500),超大規模建議使用 Swoole、Workerman、Go/Rust 等原生非阻塞框架,或引入訊息佇列(RabbitMQ/Kafka)削峰填谷。

現代替代方案

  • GuzzleHttp\Client:支援 Promise/非阻塞、中介軟體、重試、Mock 測試
  • Swoole Coroutine\Http\Client:協程級並發、極高效能、原生支援 HTTP/2
  • ReactPHP / Amp:事件驅動非阻塞 I/O 框架

總結

cURL Multi 是 PHP 單進程併發網路請求的基石。掌握 curl_multi_select 事件驅動寫法、合理設定超時與併發上限、完善錯誤處理,即可構建高效穩定的批次請求系統。隨著業務增長,適時遷移至協程框架或異步架構,才是長久之計。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved