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

PHP 並發 HTTP 請求實戰:基於 curl_multi 的 Http_MultiRequest 類封裝與性能對比

     編輯:PHP 相關知識

為什麼需要並發請求?

順序串行請求多個 API 時,總耗時 = 所有請求耗時之和。並發請求可將總耗時降為 **最慢單個請求的耗時**,對聚合多個微服務、爬蟲、批量推送等場景至關重要。

核心原理:curl_multi

  • curl_multi_init() 創建批處理句柄
  • curl_multi_add_handle() 添加多個單獨 curl handle
  • curl_multi_exec() 循環執行,直到無運行中句柄
  • curl_multi_getcontent() 獲取各句柄響應
  • curl_multi_remove_handle() 清理

封裝類:Http_MultiRequest

<?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;
    }
}
?>

關鍵優化:curl_multi_select

原文中的 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

生產環境最佳實踐

  1. 錯誤處理:檢查 curl_errno($ch)、curl_getinfo($ch, CURLINFO_HTTP_CODE),區分網絡錯誤、HTTP 錯誤、超時。
  2. 超時控制:設置 CURLOPT_TIMEOUT (總超時) 和 CURLOPT_CONNECTTIMEOUT (連接超時),防止單個慢請求阻塞整個批次。
  3. 並發限制:大量 URL (如 > 100) 時分批處理,避免耗盡文件描述符或觸發目標服務器限流。
  4. 異步非阻塞框架:高併發長連接場景建議使用 Swoole、Workerman、OpenSwoole 或 Go/Node.js 實現真正的異步 I/O,PHP-FPM + curl_multi 仍受限於進程模型。
  5. 現代 HTTP 客戶端:GuzzleHttp (底層封裝 curl_multi、支持 Promise/異步)、Symfony HttpClient、HTTPlug,提供更優雅的 API、中間件、重試、緩存等企業級特性。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved