You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用libcurl实现与curl --parallel通配符等效的高性能请求

用libcurl复现curl --parallel的高性能批量请求实现方案

一、curl --parallel的核心实现原理

  • HTTP/2多路复用优先:默认优先使用HTTP/2协议,在单个TLS连接上同时发起数十个请求,彻底避免了大量TCP/TLS握手的开销,这是性能爆发的核心。
  • 原生高效I/O多路复用:内部直接采用系统原生的I/O机制(如epoll、kqueue)管理套接字事件,没有依赖第三方事件框架的额外封装开销,事件调度更直接高效。
  • 轻量并发调度:维护请求队列,严格按照parallel-max控制并发数,一个请求完成后立即调度下一个,最大化利用带宽资源,同时避免服务器过载。
  • 内置重试逻辑:--retry和--retry-all-errors的重试逻辑直接集成在请求处理流程中,失败请求快速重新入队,无需额外异步框架的调度成本。

二、libevent示例性能差的核心原因

  • 框架封装开销:libevent作为通用事件框架,比curl原生I/O多了一层抽象,在高并发小请求场景下,回调调度、事件管理的额外开销被大幅放大。
  • 配置未对齐命令行:官方示例默认未开启HTTP/2、连接池复用,也没有配置与命令行一致的重试策略和并发数限制,完全没有利用到curl的高性能特性。
  • 事件循环低效:libevent的事件循环可能存在不必要的唤醒逻辑,加上示例代码中未优化磁盘写入等操作,导致CPU空转、资源浪费。

三、用libcurl复现高性能的关键配置与实现步骤

1. 强制启用HTTP/2与连接复用

  • 初始化easy handle时,强制指定HTTP/2版本:
    curl_easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
    
  • 配置连接池参数,对齐命令行的并发限制:
    curl_multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, 150L);
    curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, 150L); // HTTP/2单连接多路复用,设为并发数即可
    curl_easy_setopt(easy, CURLOPT_KEEPALIVE, 1L); // 开启长连接
    
  • 禁用无关协议,减少协商开销:
    curl_easy_setopt(easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
    

2. 直接使用curl原生multi_socket接口(弃用libevent)

用系统原生I/O(如epoll)替代libevent,减少中间层开销,核心实现逻辑:

// 初始化multi handle
CURLM* multi = curl_multi_init();
int running_handles = 0;

// 批量添加所有请求到multi handle
for (const auto& url : url_list) {
    CURL* easy = curl_easy_init();
    // 基础配置
    curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
    curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1L);
    // 重试配置对齐命令行
    curl_easy_setopt(easy, CURLOPT_RETRY, 10L);
    curl_easy_setopt(easy, CURLOPT_RETRY_ALL_ERRORS, 1L);
    // 写文件配置(自定义回调减少磁盘I/O开销)
    curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, buffered_write_callback);
    curl_easy_setopt(easy, CURLOPT_WRITEDATA, new FileWriter(url)); // 自定义带缓冲的文件写入类
    // 添加到multi handle
    curl_multi_add_handle(multi, easy);
}

// 用epoll实现事件循环
int epfd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event ev, events[1024];

// 初始触发multi socket动作
curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);

// 主事件循环
while (running_handles > 0) {
    long timeout = 0;
    curl_multi_timeout(multi, &timeout);
    // 等待事件,避免无意义轮询
    int num_events = epoll_wait(epfd, events, 1024, timeout > 0 ? timeout : 100);
    
    for (int i = 0; i < num_events; i++) {
        curl_socket_t sockfd = (curl_socket_t)events[i].data.fd;
        int action = 0;
        if (events[i].events & EPOLLIN) action |= CURL_CSELECT_IN;
        if (events[i].events & EPOLLOUT) action |= CURL_CSELECT_OUT;
        // 处理事件并更新multi状态
        curl_multi_socket_action(multi, sockfd, action, &running_handles);
    }

    // 处理完成的请求
    CURLMsg* msg;
    int msgs_left;
    while ((msg = curl_multi_info_read(multi, &msgs_left))) {
        if (msg->msg == CURLMSG_DONE) {
            CURL* easy = msg->easy_handle;
            // 清理资源
            curl_multi_remove_handle(multi, easy);
            curl_easy_cleanup(easy);
        }
    }
}

// 清理资源
curl_multi_cleanup(multi);
close(epfd);

3. 优化磁盘I/O与重试逻辑

  • 实现带缓冲的写入回调:先将数据写入内存缓冲区,达到阈值后再写入磁盘,减少系统调用次数:
    size_t buffered_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) {
        FileWriter* writer = static_cast<FileWriter*>(userdata);
        return writer->write(ptr, size * nmemb);
    }
    
  • 确保重试逻辑完全对齐命令行:CURLOPT_RETRY设为10,CURLOPT_RETRY_ALL_ERRORS设为1,覆盖所有错误类型的重试。

4. 减少CPU空转

  • 利用curl_multi_timeout获取精确超时时间,避免epoll_wait无限制阻塞或频繁轮询。
  • 禁用curl的调试输出、进度条等非必要功能:
    curl_easy_setopt(easy, CURLOPT_VERBOSE, 0L);
    curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1L);
    

四、性能验证与调优

  • 监控连接数:用ss -t查看与目标服务器的连接数,正常应为1个(HTTP/2多路复用生效)。
  • 调整并发数:保持parallel-max与命令行一致(150),若带宽未跑满可适当上调,但需遵循服务器的并发限制。
  • 磁盘优化:如果写入是瓶颈,可将多个小文件合并写入,或使用异步I/O接口(如aio_write)。

内容的提问来源于stack exchange,提问作者Oliver Schönrock

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 20:10:57