5月28日 02:37

cURL 性能优化有哪些关键手段?

为什么要关注 cURL 性能

cURL 是后端开发、运维和测试中最常用的命令行 HTTP 工具。默认配置下,cURL 每次请求都重新建立 TCP 连接和 TLS 握手,批量调用时性能损耗显著。掌握超时、重试、连接复用、并发和压缩等优化手段,能让生产环境的 API 调用速度提升数倍。

超时与速率控制

超时是生产环境的第一道防线。cURL 提供两级超时:

  • --connect-timeout:TCP 连接建立的最大等待时间
  • --max-time:整个请求(含传输)的最大耗时
bash
# 连接超时 10 秒,整体超时 30 秒 curl --connect-timeout 10 --max-time 30 https://api.example.com # 7.68.0+ 支持毫秒级超时 curl --connect-timeout 3.5 --max-time 10.5 https://api.example.com

遇到慢速传输时,--speed-time--speed-limit 可以主动中断卡住的连接:

bash
# 如果连续 5 秒速度低于 100 字节/秒,自动中断 curl --speed-time 5 --speed-limit 100 https://api.example.com/large-file.zip -O

速率限制用 --limit-rate,在带宽敏感场景下控制下载速度:

bash
curl --limit-rate 1M https://example.com/large-file.zip -O

重试机制

网络请求天生不可靠,重试是保障可靠性的核心手段。

bash
# 失败自动重试 3 次 curl --retry 3 https://api.example.com # 重试间隔 2 秒,防止立即重试加重服务端压力 curl --retry 3 --retry-delay 2 https://api.example.com # 限定重试总耗时,避免无限等待 curl --retry 5 --retry-delay 1 --retry-max-time 30 https://api.example.com # 连接被拒绝时也重试(默认只重试超时类错误) curl --retry 3 --retry-connrefused https://api.example.com

关键细节:--retry 默认只对超时、5xx 错误和连接失败重试,不会对 4xx 重试。如果需要针对特定 HTTP 状态码重试,需要脚本层面处理。--retry-delay 只在两次重试之间生效,首次请求不受影响。

连接复用

HTTP Keep-Alive 是 cURL 性能优化中收益最高的一项。一次 TCP + TLS 握手通常需要 100-300ms,复用连接直接省掉这笔开销。

bash
# 保持连接 60 秒 curl --keepalive-time 60 https://api.example.com

命令行 cURL 在单次执行中自动复用连接。但跨进程调用时无法复用——这是命令行 cURL 的固有限制,需要 libcurl 句柄复用才能解决:

c
// libcurl 句柄复用示例 CURL *handle = curl_easy_init(); // 第一次请求 curl_easy_setopt(handle, CURLOPT_URL, "https://api.example.com/users"); curl_easy_perform(handle); // 第二次请求复用同一连接 curl_easy_setopt(handle, CURLOPT_URL, "https://api.example.com/products"); curl_easy_perform(handle); curl_easy_cleanup(handle);

多 handle 场景下,用 curl_share_setopt 共享 DNS 缓存和 Cookie,进一步减少重复开销。

DNS 解析也有缓存收益。--resolve 可以跳过 DNS 查询:

bash
# 直接指定 IP,跳过 DNS 解析 curl --resolve api.example.com:443:203.0.113.50 https://api.example.com

这对调试 CDN 回源、绕过 DNS 劫持、压测时固定后端 IP 都有用。

HTTP/2 多路复用

HTTP/2 在单条 TCP 连接上并行传输多个请求,从根本上解决了 HTTP/1.1 的队头阻塞问题:

bash
# 强制使用 HTTP/2 curl --http2 https://api.example.com # 优先协商 HTTP/2,失败回退 HTTP/1.1 curl --http2-prior-knowledge https://api.example.com

配合连接复用,HTTP/2 的收益最大:多个 API 请求共享一条连接,省去多次握手和队头等待。适合微服务网关、GraphQL 批量查询等场景。

并发请求

cURL 7.66+ 原生支持并行传输,使用 -Z--parallel)标志:

bash
# 并行下载多个 URL curl -Z -OL https://example.com/a.json https://example.com/b.json # 控制最大并发数 curl -Z --parallel-max 5 -OL https://example.com/file{1..10}.zip

旧版本用 shell 方式实现并发:

bash
# 后台进程 + wait for url in "${urls[@]}"; do curl -s "$url" -o "$(basename $url).json" & done wait # GNU Parallel,更精细的并发控制 cat urls.txt | parallel -j 4 curl -s {} -o {/}.json

选择建议:少量 URL 用 -Z 即可,批量任务推荐 GNU Parallel,便于控制并发数和失败重试。

压缩传输

--compressed 让 cURL 在请求头添加 Accept-Encoding,服务端返回压缩响应后自动解压:

bash
curl --compressed https://api.example.com

JSON API 响应通常能压缩 60-80%,对移动端和带宽敏感场景效果显著。也可以手动指定压缩算法:

bash
curl -H "Accept-Encoding: gzip, deflate, br" https://api.example.com

注意:--compressed 在服务端不支持压缩时不会报错,cURL 会正常接收未压缩的响应。

TCP 与 TLS 优化

--tcp-nodelay 禁用 Nagle 算法,减少小包传输延迟,适合交互式 API 调用:

bash
curl --tcp-nodelay --tcp-fastopen https://api.example.com

TLS 握手是 HTTPS 请求中耗时的环节,优化点包括:

bash
# 强制使用 TLS 1.2 及以上(拒绝旧协议) curl --tlsv1.2 --tls-max tls1.3 https://api.example.com # TLS 会话复用(libcurl 句柄复用时自动生效) # CA 缓存减少证书链重复加载(libcurl 7.84+)

生产环境务必验证证书,不要用 -k 跳过验证。密码等凭证不要写在命令行里:

bash
# -u 只输用户名,cURL 会提示输入密码 curl -u "username" https://api.example.com # 更好:用环境变量 curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com

性能分析

-w--write-out)是 cURL 性能诊断的核心工具,可以输出请求各阶段耗时:

bash
curl -w "DNS: %{time_namelookup}s Connect: %{time_connect}s SSL: %{time_appconnect}s TTFB: %{time_starttransfer}s Total: %{time_total}s Size: %{size_download}B Speed: %{speed_download}B/s " -o /dev/null -s https://api.example.com

各指标含义:

指标含义关注场景
time_namelookupDNS 解析耗时CDN 选路、DNS 劫持排查
time_connectTCP 连接建立耗时网络延迟、连接池耗尽
time_appconnectTLS 握手完成耗时证书链过长、协议协商慢
time_starttransfer首字节到达耗时(TTFB)服务端处理慢、排队过长
time_total整体耗时端到端性能评估

定位思路:如果 time_namelookup 高,查 DNS;time_connect 高,查网络或连接池;time_appconnect 高,查 TLS 配置;time_starttransfer 高但前面指标正常,查服务端。

把格式写入文件可以复用:

bash
cat > perf-format.txt << 'EOF' timestamp:%{time_total} dns:%{time_namelookup} connect:%{time_connect} ssl:%{time_appconnect} ttfb:%{time_starttransfer} size:%{size_download} speed:%{speed_download} EOF curl -w "@perf-format.txt" -o /dev/null -s https://api.example.com >> perf.log

大文件与断点续传

bash
# 分块下载 curl -r 0-10485760 https://example.com/large-file.zip -o part1.zip curl -r 10485761-20971520 https://example.com/large-file.zip -o part2.zip # 断点续传 curl -C - -O https://example.com/large-file.zip

分块下载 + 断点续传组合使用:先分块下载,某块中断后用 -C - 续传,最后用 cat part*.zip > large-file.zip 合并。

流式处理避免大文件占满内存:

bash
# 流式处理 JSON 响应 curl -s https://api.example.com/stream | jq '.[] | .name'

生产级脚本模板

bash
#!/bin/bash # 生产级 API 调用脚本 API_URL="https://api.example.com/v1/data" TOKEN="${API_TOKEN:-$(cat ~/.api_token)}" TIMEOUT=30 RETRY=3 api_call() { curl -s -S --connect-timeout 10 --max-time "$TIMEOUT" --retry "$RETRY" --retry-delay 2 --retry-connrefused --compressed --tlsv1.2 -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -H "Accept: application/json" -H "User-Agent: MyApp/1.0" -w " {"status":%{http_code},"time":%{time_total},"size":%{size_download}} " "$@" } response=$(api_call "$API_URL") if [ $? -eq 0 ]; then echo "$response" | jq '.' else echo "Request failed" >&2 exit 1 fi

可维护性方面,常用配置写入 ~/.curlrc

shell
verbose connect-timeout = 10 max-time = 60 retry = 3

日志监控脚本示例:

bash
#!/bin/bash while true; do STATUS=$(curl -w "%{http_code}" -o /dev/null -s --max-time 5 https://api.example.com/health) echo "$(date '+%Y-%m-%d %H:%M:%S') | Status: $STATUS" [ "$STATUS" != "200" ] && echo "API unhealthy!" | mail -s "API Alert" admin@example.com sleep 60 done

优化效果对比

优化项优化前优化后典型提升
连接复用每次新建连接Keep-Alive延迟降低 30-50%
压缩传输原始大小Gzip/Brotli体积减少 60-80%
HTTP/2 多路复用队头阻塞单连接并行并发延迟降低 50%+
并发请求串行执行并行处理吞吐提升 3-5 倍
DNS 缓存每次解析--resolve/本地缓存延迟降低 10-20ms
断点续传重新下载续传节省已完成部分的带宽

追问

  • cURL 的 --retry 和应用层面的指数退避重试有什么区别?什么时候该用哪种?
  • 命令行 cURL 的连接复用有什么局限?libcurl 句柄复用如何突破这个限制?
  • --compressed 在服务端不支持压缩时行为是什么?会不会导致请求失败?
  • HTTP/2 多路复用和 -Z 并行传输有什么区别?各自适合什么场景?
标签:cURL