如何用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
相关产品推荐
相关产品推荐

