3. HTTP 协议中的超时机制与超时头字段

3.1 从 TCP 连接说起

聊 HTTP 超时,我习惯先拉到 TCP 层面。为什么?因为 HTTP 是跑在 TCP 上的。TCP 连不上,HTTP 再快也没用。

TCP 连接超时会触发三次握手。客户端发 SYN,等服务端回应 SYN-ACK。这个等待时间,就是 tcp_syn_timeout。内核参数默认 20 秒到 127 秒不等。

我在项目中遇到过这样一个场景:客户端访问内部服务,时不时卡住 60 多秒。查了半天,原来是集群里某个节点挂了。客户端在等 SYN-ACK 等不到,直到 60 秒后才报超时。你说这个体验能好吗?

核心观点: HTTP 超时的底层依赖是 TCP 超时。TCP 链路不通,HTTP 层面设再小的超时都没用。

3.2 HTTP 层面的几个关键超时

HTTP 协议本身没有规定超时字段。倒是应用层和中间件里,我们人为定义了这几个常用的超时概念。我个人习惯把它们分成四类。

超时类型 时间范围 默认值(常见) 配置层级
连接超时 (Connect Timeout) 100ms – 30s 5s – 10s 客户端 / 代理
读取超时 (Read Timeout) 500ms – 5min 30s – 120s 客户端 / 代理
请求超时 (Request Timeout) 全链路累计时间 60s – 300s 网关 / LB
空闲超时 (Idle Timeout) Keep-Alive 保留时间 60s - 120s 服务端 / 代理

你想想看,这四个超时如果配乱了,会出什么情况?

  • 连接超时配得太大。客户端等 30 秒才觉得连不上,用户体验极差。
  • 读取超时配得太小。服务端还没返回数据,客户端就断开。业务数据丢了。
  • 请求超时跟读取超时搞反。请求超时比读取超时还短,请求永远跑不完。
  • 空闲超时设置不一致。客户端 keep-alive 设 120 秒,Nginx 设 60 秒。连接被 Nginx 关闭,客户端还在复用,直接报 502。
避坑指南: 我曾经遇到过一个经典场景,Nginx 的 proxy_read_timeout 设成 60s,后端一个批处理接口正常耗时 45s。好巧不巧那天数据库慢查询,某个请求花了 55s。Nginx 等 60s 后返回 503,客户端重试,又触发了慢查询——整个系统雪崩。所以超时值不要卡太死,建议留 20% 的 buffer。

3.3 超时头字段:谁说了算?

HTTP 协议里能直接跟超时扯上关系的头其实很少。我整理了一下,真正有标准定义的,主要就这几个。

字段名 方向 作用 是否标准
Connection 双向 控制连接是否复用,间接影响超时 标准
Keep-Alive: timeout=N 服务端 通知客户端保持空闲的最大时间 非标准但广泛实现
Trailer + Timeout(极少用) 服务端 分块传输中声明超时时间 非标准, 兼容性差
Retry-After 服务端 告诉客户端多久后再重试 标准

说白了,HTTP 本身并不强制客户端必须遵守这些超时指令。它是一个建议性的机制。比如 Nginx 发了个 Keep-Alive: timeout=30,客户端完全可以不理,照发 60 秒的空闲请求。最终超时靠的还是那一方先等不下去。

实用技巧: 我建议在网关层统一设置 Connection: close 来关闭长连接。这样做虽然牺牲了一点性能,但在超时排障时好处很明显——你就不需要同时调试客户端、代理、服务端三层不同的空闲超时配置了。

3.4 排障中怎么看超时?

当接到一个超时投诉,我个人习惯按这个顺序快速扫一圈。下面这张图能帮你看清楚:

HTTP 超时排查全景图 客户端 TCP 连接 S Connect Timeout TLS 握手超时 请求发送超时 服务端处理超时 S Read Timeout 响应体传输超时 空闲/Idle超时 排障步骤 1. 确认是哪一层超时(客户端/代理/服务端) 2. 检查对应层的 timeout 配置值与实际耗时 3. 抓包验证 TCP 状态,区分超时类型

实际排障时,我一般先抓包看 TCP 状态。如果客户端一直发 SYN 没有 SYN-ACK,那是连接超时。如果数据包发过去了但等不到响应,那就是读取超时。说白了,超时的本质是某个阶段等待的时间超过了你的配置值。

总结一句话: 超时不是 bug,是保护机制的触发。正确的做法不是把超时设得无限大,而是搞清楚「为什么」它需要那么长时间。

3.5 一个常见的配置陷阱

嗯,这里要特意提一下。很多中间件(比如 Nginx 的 proxy_connect_timeout、Apache 的 Timeout)都有默认值。我见过太多人只调业务代码,忘了检查代理层的超时。我曾经帮人排过一个案例:业务接口本身 1 秒就返回,但客户端总报 30 秒超时。查到最后,是中间某层代理的 proxy_read_timeout 设成了 5 秒,但后端响应偶尔 6 秒。客户端等了 30 秒,其实代理早就断掉了。

# Nginx 配置示例
location /api/ {
    # 连接后端超时
    proxy_connect_timeout 5s;
    # 等待后端响应超时
    proxy_read_timeout 60s;
    # 向后端发送请求体超时
    proxy_send_timeout 10s;
    # 不推荐将读超时设为过小,尤其批处理接口
    # 可以用 keepalive_timeout 控制连接复用
    keepalive_timeout 65s;
}

你想想看,如果这里 proxy_read_timeout 设得比上面介绍的读取超时还小,那即使客户端本身设的是 120 秒,也照样会收到 502。所以排超时,一定要从请求链两端往中间查,一层一层往里剥。