线上最常见的一句反馈就是“接口慢”。但如果不把耗时拆开,这句话几乎没有排障价值。慢到底慢在 DNS、TCP 建连、TLS 握手、服务处理,还是响应体下载,必须拆出来才能继续判断。

HTTP 排障最容易犯的错误,是把总耗时当成唯一指标。真正有价值的是把总时间拆成多个阶段,然后看瓶颈究竟落在哪一步。

为什么 curl 特别适合做第一层判断

浏览器适合使用,但不适合做最小现场分析。curl 的价值在于它足够轻、足够可控,而且可以把请求过程拆成多个时间段输出出来。

一个最常用的命令如下:

1
curl -o /dev/null -s -w 'dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} starttransfer:%{time_starttransfer} total:%{time_total}\n' https://example.com

这里几个字段非常重要。

  • time_namelookup 表示 DNS 解析完成时间。
  • time_connect 表示 TCP 建连完成时间。
  • time_appconnect 表示 TLS 握手完成时间。
  • time_starttransfer 表示收到首字节时间,也常被理解为 TTFB。
  • time_total 表示整个请求结束时间。

只要把这些时间拆开,很多“接口慢”的问题就已经不再模糊。

一次标准的分析方式

假设某个接口总耗时 2 秒,不要急着说后端慢,先看每段耗时分布。

如果 dns 很高,那就先排 DNS。

如果 connect 很高,那就先看 TCP 建连和链路质量。

如果 tls 很高,那就重点看证书、握手和协商过程。

如果前面都很快,但 starttransfer 很高,那更像是服务端处理、缓存未命中、回源慢或者中间代理等待。

如果首字节很快,但 total 很慢,那要继续看响应体是否过大、是否有流式返回、是否存在客户端接收瓶颈。

配合详细输出一起看

时间拆解之外,再配合 -v 看过程会更完整。

1
curl -v https://example.com

这里你可以观察到。

  • 实际连接的 IP。
  • 是否复用了已有连接。
  • 请求头和响应头。
  • 是否经过重定向。
  • TLS 握手的一些关键信息。

如果服务经过 CDN、反向代理或网关,响应头里还常常会带一些非常有价值的排障线索,比如缓存命中状态、边缘节点标识、上游处理时间等。

一条更贴近实战的排查链路

面对慢请求,我一般会先用下面这个组合。

1
curl -v -o /dev/null -s -w 'dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} starttransfer:%{time_starttransfer} total:%{time_total}\n' https://example.com/api

如果发现 connect 和 tls 都很快,而 starttransfer 明显偏高,就说明网络建连大概率没问题,应更多怀疑服务处理、缓存命中、回源、数据库或下游依赖。

如果 dns 明显偏高,就先去对比不同 DNS 结果。

如果 connect 明显偏高,再结合 ss、tcpdump 看连接阶段是否存在重传或等待。

如果 tls 明显偏高,就需要进一步看 HTTPS 握手细节。

这就是为什么 HTTP 排障不能只盯着一个总耗时数字。因为同样是“2 秒”,背后可能是完全不同的系统问题。

常见误判

第一个常见误判,是把 TTFB 慢直接等同于应用代码慢。实际上,负载均衡等待、反向代理排队、CDN 回源、上游链路拥塞,都可能让首字节变慢。

第二个常见误判,是用浏览器手感代替数据。页面觉得慢,不代表接口真的慢;接口慢,也不代表 DNS 或 TLS 慢。没有时间拆解,就很容易把问题归因错。

第三个常见误判,是忽略重定向和连接复用。有时第一次慢是因为握手和重定向,后续请求快很多;如果不看过程,很容易误以为服务本身抖动。

一个值得自己做的小实验

你可以对同一个站点连续发几次请求,并记录每一段时间。

1
2
3
curl -o /dev/null -s -w 'dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} starttransfer:%{time_starttransfer} total:%{time_total}\n' https://example.com
curl -o /dev/null -s -w 'dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} starttransfer:%{time_starttransfer} total:%{time_total}\n' https://example.com
curl -o /dev/null -s -w 'dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} starttransfer:%{time_starttransfer} total:%{time_total}\n' https://example.com

然后观察。

  • 第一次和后两次相比,是否有明显差异。
  • 是哪一段时间差异最大。
  • 如果请求一个静态资源和一个动态接口,两者瓶颈分别在哪。

这个实验特别适合训练“先拆阶段,再下结论”的习惯。

小结

HTTP 请求耗时分析真正的关键,不是把请求发出去,而是把时间拆开。只要你能用 curl 把 DNS、建连、TLS、首字节和总耗时分开看,“接口慢”这类原本非常笼统的问题,就会开始变成结构化问题。排障最怕模糊,而时间拆解恰好就是把模糊打碎的第一步。