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

PHP cURL 多執行緒(curl_multi)原理深度解析與高併發爬蟲類別設計

     編輯:PHP 相關知識

為何需要 curl_multi?

單執行緒循環呼叫 curl_exec 會依序等待每個請求完成,總耗時 = Σ(單請求耗時)。curl_multi 允許並發發送多個請求,總耗時 ≈ max(單請求耗時),大幅提升爬蟲/聚合 API 效率。

核心流程六步驟

  1. curl_multi_init() 建立批處理 handle($mh)
  2. 對每個 URL:curl_init() → 設定選項 → curl_multi_add_handle($mh, $ch)
  3. 持續呼叫 curl_multi_exec($mh, $active) 驅動傳輸
  4. 利用 curl_multi_select($mh) 等待網路事件(避免 CPU 100%)
  5. 當 $active === 0 表示全部完成,逐個 curl_multi_getcontent($ch) 取得結果
  6. curl_multi_remove_handle() → curl_close() → curl_multi_close() 清理資源

官方範例的致命缺陷與修正

官方文件範例在 do-while 迴圈中未呼叫 curl_multi_select,導致忙等待佔滿 CPU。正確寫法:


do {
    $mrc = curl_multi_exec($mh, $active);
} while ($mrc === CURLM_CALL_MULTI_PERFORM);

while ($active && $mrc === CURLM_OK) {
    if (curl_multi_select($mh) !== -1) { // 有網路事件才繼續
        do {
            $mrc = curl_multi_exec($mh, $active);
        } while ($mrc === CURLM_CALL_MULTI_PERFORM);
    }
}

高併發爬蟲類別設計重點(生產級)


class MultiCurl {
    private $mh;
    private $handles = [];
    private $maxConcurrency = 10;
    private $callbacks = [];

    public function __construct($maxConcurrency = 10) {
        $this->maxConcurrency = $maxConcurrency;
        $this->mh = curl_multi_init();
    }

    public function addTask($url, callable $callback, array $options = []) {
        $this->callbacks[] = ['url' => $url, 'cb' => $callback, 'opt' => $options];
    }

    public function run() {
        $pending = $this->callbacks;
        $running = [];

        while ($pending || $running) {
            // 補滿併發槽
            while (count($running) < $this->maxConcurrency && $pending) {
                $task = array_shift($pending);
                $ch = curl_init($task['url']);
                $defaults = [
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_HEADER => false,
                    CURLOPT_FOLLOWLOCATION => true,
                    CURLOPT_TIMEOUT => 30,
                    CURLOPT_SSL_VERIFYPEER => false,
                ];
                curl_setopt_array($ch, $defaults + $task['opt']);
                curl_multi_add_handle($this->mh, $ch);
                $running[(int)$ch] = ['ch' => $ch, 'cb' => $task['cb']];
            }

            // 驅動傳輸
            do { $mrc = curl_multi_exec($this->mh, $active); } while ($mrc === CURLM_CALL_MULTI_PERFORM);

            // 等待事件
            if ($active) curl_multi_select($this->mh);

            // 檢查完成的 handle
            while ($info = curl_multi_info_read($this->mh)) {
                $ch = $info['handle'];
                $task = $running[(int)$ch];
                unset($running[(int)$ch]);

                $content = curl_multi_getcontent($ch);
                $error = curl_error($ch);
                $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

                // 執行回調
                try {
                    ($task['cb'])($content, $httpCode, $error, $task['ch']);
                } catch (Throwable $e) {
                    error_log("Callback error: " . $e->getMessage());
                }

                curl_multi_remove_handle($this->mh, $ch);
                curl_close($ch);
            }
        }
        curl_multi_close($this->mh);
    }
}

// 使用範例
$crawler = new MultiCurl(20);
$urls = ['https://api.example.com/1', 'https://api.example.com/2', /* ... */];
foreach ($urls as $url) {
    $crawler->addTask($url, function($content, $code, $err, $ch) {
        if ($code === 200) {
            $data = json_decode($content, true);
            // 處理資料、存資料庫、發佈隊列...
        }
    });
}
$crawler->run();

關鍵優化參數

參數建議值說明
CURLOPT_TIMEOUT10-30單請求最長等待秒數
CURLOPT_CONNECTTIMEOUT5連線建立逾時
CURLOPT_TCP_KEEPALIVE1保持長連線
CURLOPT_FORBID_REUSE0允許連線重用(HTTP/1.1 Keep-Alive)
CURLOPT_MAXCONNECTS等於併發數連線快取池大小

常見坑點與對策

  • DNS 解析阻塞:大量並發時 DNS 可能成為瓶頸,啟用 CURLOPT_DNS_CACHE_TIMEOUT 或使用本地 DNS 快取。
  • 記憶體洩漏:及時 curl_multi_remove_handle 並 curl_close,避免 handle 累積。
  • SSL 握手耗時:重用連線、啟用 CURLOPT_SSL_SESSIONID_CACHE。
  • 錯誤重試:在回調中判斷 $httpCode >= 500 || $error,重新 addTask(需限制重試次數)。
  • 最大併發限制:libcurl 內部通常限制 500-512 個同時 handle,超過會失敗;大規模請求請分批或多進程。

總結

curl_multi 配合 curl_multi_select 事件驅動模型,是 PHP 高併發 HTTP 客戶端的核心。封裝為可控制併發、支援回調、自動重試的類別,可構建穩健的爬蟲、API 聚合、微服務閘道等系統。

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