上一篇算了一笔账:新建一个 TCP 连接,光握手就要 1 个 RTT。如果一个页面要发 30 个请求,难道要握手 30 次?

HTTP/1.0 时代还真是这样——每个请求建一次连接,请求完就断。这在当年页面简单、请求数少的年代还能忍,但现在的页面动辄几十上百个请求,每次都握手,光建连接就把时间吃光了。

keep-alive 就是为解决这个问题生的:握一次手,多跑几个请求。这一篇讲它怎么工作、能省多少、以及它治标不治本的地方。

短连接 vs 长连接

先看清两种模式的差别:

短连接 N请求N握手 vs 长连接 N请求1握手

  • 短连接(HTTP/1.0 默认):每个请求都走一次完整的「TCP握手 → 发请求 → 收响应 → 断开」。N 个请求 = N 次握手 + N 次断开。响应头里 Connection: close 表示用完即断。
  • 长连接(HTTP/1.1 默认):握一次手,连接保持打开,后续请求复用这条连接。N 个请求 = 1 次握手。响应头里 Connection: keep-alive 表示保持连接。

省下来的是什么?是 N-1 次握手的 RTT 开销。如果 RTT 是 50ms,30 个请求从「30×50ms = 1500ms 握手」降到「1×50ms = 50ms 握手」,光建连接就省了 1.45 秒。

这就是 HTTP/1.1 把 keep-alive 设为默认行为的原因——RFC 7230 规定 HTTP/1.1 连接默认就是持久的,除非显式声明 Connection: close,否则连接保持复用。

复用的边界:一条连接不能无限复用

keep-alive 不是「建一条连接用一辈子」。它有两个边界:

第一,idle timeout。连接空闲(没数据传输)超过一定时间就会被关闭。服务器有个 Keep-Alive: timeout=5 头告诉客户端「我 5 秒没收到你的请求就断」。这个值是权衡:太长,服务器要维持大量空闲连接,占内存和文件描述符;太短,连接老是被断,复用价值就没了。Nginx 默认 75 秒,很多场景会调短到 10-30 秒。

第二,浏览器的连接数上限。一条连接上请求是串行的(HTTP/1.1 管线化几乎没人用,下一篇讲),所以浏览器会开多条连接并行。但同域名最多开 6 条(HTTP/1.1 的约定),超过的请求要排队等。

浏览器连接池:同域名 6 连接上限

这个 6 连接上限是 HTTP/1.1 时代的硬约束。如果一个页面要对同一个域名发 30 个请求,浏览器开满 6 条连接,每条串行跑 5 个,6 条并行后总耗时大约是 5 个请求的串行时间。如果请求数再多,后面的就得排队。

这就是为什么早期前端有「域名分片(domain sharding)」的优化——把静态资源分散到 static1.xxx.comstatic2.xxx.com 多个域名,每个域名都能开 6 条连接,凑出更多并行度。但这是 workaround,治标不治本,HTTP/2 的多路复用才是正解(第 26 篇讲)。

keep-alive 的代价

复用连接不是没有成本的:

keep-alive 的收益 vs 代价

收益是明确的:省掉重复握手的 RTT。连接复用率越高,省得越多。

代价有两个:

  • idle 连接占资源。每个保持的连接都占服务器的内存和文件描述符。如果服务器维持 10 万个 idle 连接,每个哪怕只占几 KB,也是几百 MB 的开销。这是 SYN Flood 之外另一种「连接耗尽」攻击(slowloris)的原理——攻击者开一堆连接但不发完整请求,占满服务器的连接池。
  • 队头阻塞还在。keep-alive 解决了「重复握手」的问题,但没解决「一条连接上请求串行」的问题。第一个请求慢了,后面排队的请求全得等。这是 HTTP/1.1 的结构性问题,keep-alive 治不了,要等 HTTP/2。

取舍与边界

keep-alive 在实践中有几个值得注意的点:

  • 启用要双方同意。HTTP/1.1 默认长连接,但 HTTP/1.0 要显式声明 Connection: keep-alive 才复用。如果一端不支持,连接用完即断。
  • Content-Length 或 chunked 是复用的前提。长连接上,服务器必须让客户端知道每个响应在哪里结束(靠 Content-Length 或 chunked),否则客户端不知道下一个响应什么时候开始,连接就没法复用。
  • 代理可能改写 keep-alive 行为。有些反向代理会在自己的那一跳用长连接,但到后端用短连接,或者反过来。排查「为什么连接没复用」时要沿着链路逐跳看。

收束:keep-alive 是止痛药,不是治本

keep-alive 解决了重复握手的浪费,让连接复用成为默认行为。但它没动 HTTP/1.1 的根本问题——一条连接上请求串行带来的队头阻塞。这是下一篇的主题,也是 HTTP/2 多路复用要填的洞。

理解 keep-alive 的收益和它的天花板,你才会明白为什么后续的演进是必要的:keep-alive 把「连接建立」的开销摊薄了,但「请求串行」的瓶颈还在。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。

我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。

如果你想继续跟完这套「图解 HTTP」,欢迎关注公众号 「十三Tech」。后续会按 URL 与报文、连接与传输、缓存与协商、安全与边界、HTTP/2 与 HTTP/3 这条线更新。

十三Tech公众号二维码