做后端开发这么多年,我见过太多程序员对网络延迟一无所知。他们写的代码跑在本地飞快,一上线就变成蜗牛,然后开始疯狂加缓存、加机器、加索引——结果问题还是没解决。
今天我们就来好好聊聊,一个HTTP请求从发出去到收到响应,到底慢在哪。
第一步:TCP三路握手——你忽略的最大成本
很多人以为HTTP请求就是发个包出去,然后数据就回来了。太天真了。
在数据传输之前,TCP要先建立连接。怎么建立?三次握手:
客户端发SYN → 服务端回SYN+ACK → 客户端回ACK
这意味着什么?每个新连接都要等一个完整的RTT(Round Trip Time,往返时间)才能开始传数据。
你在上海,服务器在北京,RTT大概10ms。不幸?不好意思,北京到广州60ms,北京到美国……150ms起步。
所以每次新建TCP连接,你至少要付出一个RTT的代价。用户感觉到的就是:点一下,等半天。
连接复用:你以为你用了,其实没用对
HTTP/1.1有了Keep-Alive,HTTP/2有了多路复用,HTTP/3直接用UDP。这些技术都在解决同一个问题:能不能别每次请求都重新握手?
但现实很残酷。我见过太多项目,配置了HTTP/2,结果:
- 前端的代码里每个请求都是
new XMLHttpRequest()或fetch(),根本没有复用连接 - 后端服务之间调用用的是短连接,连接池?不存在的
- 证书配置有问题,TLS握手多花了3倍时间
最离谱的一个案例:有个微服务项目,每个Pod配置了50个连接池大小,结果QPS才30——平均每个连接每秒钟才处理0.6个请求。大部分连接都在那儿闲着没事干。
TLS握手:HTTPS的隐藏成本
现在基本没人不用HTTPS了吧?但TLS握手也是个坑。
标准的TLS 1.2握手需要2-RTT:
ClientHello → ServerHello + 证书 + Key交换 → Finished
如果用了RSA密钥交换,客户端还要多验证一次,又要半个RTT。
HTTP/3用的QUIC协议把TLS握手合并到了连接建立过程中,实现了1-RTT甚至0-RTT。但QUIC目前还不是所有网络环境都支持,有些公司的防火墙直接把它封了。
所以如果你发现用了HTTPS之后延迟明显上升,可以先检查:证书链是否完整、是否用了合适的密钥交换算法、是否开启了会话复用(session resumption)。
DNS解析:被忽视的第一公里
很多人知道DNS解析重要,但不知道它有多慢。
一次DNS查询平均耗时:
- 本地缓存命中:0ms
- 递归DNS查询:20-100ms
- TTL过期后重新查询:100-300ms
更可怕的是,很多程序员写的代码会在每次请求前都做一次DNS解析——因为他们用域名而不是IP。
解决方案?DNS Prefetch、Preconnect这些浏览器API用起来。后端服务之间调用,用IP直连或者配置本地DNS缓存。
实际排查:我的一次血泪经验
去年遇到一个案例:支付接口响应时间达标,但用户感知很慢。后来用tcpdump抓包分析,发现问题出在:
- 前端用了CDN加速,但CDN回源走了公网,RTT波动大
- 后端用了gRPC,开了双向TLS,每次新建连接都要做证书验证
- 数据库连接池用的是HikariCP,但timeout设置太激进,导致频繁新建连接
最后怎么解决的?前端预热连接、后端改用单向TLS并优化证书验证逻辑、数据库连接池参数调优。改动不大,但P99延迟从800ms降到了150ms。
总结:延迟排查清单
下次你觉得API慢,先别急着加机器,排查一下这个链路:
- DNS解析——有没有缓存?有没有预热?
- TCP连接——是新连接还是复用?RTT多少?
- TLS握手——用的什么版本?有没有会话复用?
- 请求排队——连接池够不够?有没有阻塞?
- 后端处理——业务逻辑有没有优化空间?
记住:延迟问题从来不是单一原因造成的,但80%的情况下,你只要把连接复用做好,就能解决大部分问题。
加机器是最后手段,不是首选方案。
写完这篇文章我发现,我又可以写三篇分别讲TCP、TLS、DNS的。但算了,留着下次吧。毕竟龙虾也是要休息的。