← 返回题目列表

HTTP 长连接(Keep-Alive)与连接复用是怎么回事?为什么能提升性能?

高频 中等 第 11 / 27 题 更新于 2026/07/28
Keep-Alive长连接连接复用性能优化

简化版

HTTP 长连接(Keep-Alive)指一次 TCP 连接建立后,多次 HTTP 请求-响应复用它,不用每次都重新握手、断开。没有长连接时,每个请求都要「三次握手建连 → 传一次数据 → 四次挥手断开」,光握手就浪费一个 RTT,还要反复经历 TCP 慢启动。开了 Keep-Alive,连接建好后保持一段时间,后续请求直接拿来用,省掉了重复握手和慢启动的开销,这就是连接复用。HTTP/1.1 默认开启 Keep-Alive;HTTP/2 更进一步,一条连接上还能并发多个请求(多路复用),把连接复用做到极致。

详细版

一、短连接的问题

HTTP/1.0 默认是短连接:每个请求一条新 TCP 连接,用完就关。加载一个有几十个资源的网页,就要建几十次连接,每次都要付出:

  • TCP 三次握手:1 个 RTT 的延迟。
  • TLS 握手(HTTPS):还要额外 1~2 个 RTT。
  • TCP 慢启动:每条新连接都从很小的拥塞窗口开始,速度慢慢爬,短连接还没爬起来就结束了。
  • 四次挥手 + TIME_WAIT:关闭也有开销,还会占用端口资源。

二、长连接(Keep-Alive)

在响应头/请求头带上 Connection: keep-alive,告诉对方「这条连接别关,我还要用」。于是:

建立连接(三次握手,一次)
  ├─ 请求1 → 响应1
  ├─ 请求2 → 响应2      ← 复用同一条连接,无需重新握手
  └─ 请求3 → 响应3
(空闲超时或达到上限后才关闭)
  • HTTP/1.1 默认开启 Keep-Alive,不写 Connection 头也是长连接;要显式关闭得写 Connection: close
  • HTTP/1.0 默认关闭,要长连接得显式写 Connection: keep-alive

三、长连接不是永久连接

服务器不会永远留着空闲连接,一般有两个限制:

  • 超时时间(如 Nginx keepalive_timeout):连接空闲多久后关闭。
  • 最大请求数:一条连接最多处理多少个请求后关闭。

四、长连接 ≠ 并发(HTTP/1.1 的局限)

HTTP/1.1 的长连接虽然复用了连接,但同一条连接同一时刻仍只能处理一个请求(一问一答),存在队头阻塞。浏览器靠同域名开 6 条并发连接来提升并行度。HTTP/2 的多路复用才实现了「一条连接上并发多个请求」,是连接复用的终极形态。

完整版教学

一、为什么连接复用如此重要:成本在「建连」而非「传输」

很多人以为网络慢主要是「数据传得慢」,其实对大量小请求来说,真正的开销在反复建立和拆除连接。建一条 TCP 连接要经历三次握手,这至少花掉一个往返时延(RTT);如果是 HTTPS,还要再叠加 TLS 握手的 1~2 个 RTT。

假设你和服务器之间 RTT 是 100ms,一个页面 30 个资源:

  • 短连接:每个资源都要重新握手,光是 TCP 握手就 30 × 100ms = 3 秒白白花在握手上(HTTPS 更夸张)。
  • 长连接:握手一次,后面 29 个请求直接复用,省下大量 RTT。

所以连接复用优化的是延迟(少几十个 RTT),这在高延迟网络(移动、跨国)上收益巨大。

二、慢启动:短连接的隐藏杀手

TCP 有个慢启动机制:新连接刚建立时,为了不冲垮网络,拥塞窗口从很小开始(比如几个 MSS),每收到一批 ACK 才翻倍增长,逐渐试探出能跑多快。

短连接的问题在于:每条连接都要从慢启动重新开始,往往数据还没传完、窗口还没涨起来,连接就关了。等于每次都在「起步阶段」龟速传输,永远达不到链路的真实带宽。

长连接复用后,连接的拥塞窗口经过前几个请求已经「热」起来(涨大了),后续请求一上来就能用较大的窗口高速传输。这是长连接除了省握手之外的第二个重要收益

记忆点:连接复用省的是两笔钱——重复握手的 RTT + 反复慢启动的低速起步。

三、Keep-Alive 的工作机制与头部

请求/响应头:

Connection: keep-alive        # 保持连接
Connection: close             # 用完关闭
Keep-Alive: timeout=60, max=100   # 建议空闲 60 秒、最多 100 个请求(HTTP/1.1 中此头较少用,多由服务器配置控制)

版本默认行为:

版本默认要改需写
HTTP/1.0短连接长连接写 Connection: keep-alive
HTTP/1.1长连接关闭写 Connection: close

如何判断响应边界? 长连接下多个响应挤在一条连接上,接收端必须知道每个响应到哪结束:

  • Content-Length 明确声明响应体长度;
  • 或用 Transfer-Encoding: chunked 分块传输,最后一个空块表示结束。

没有这两者之一,长连接就无法切分响应,只能退回「读到连接关闭为止」的短连接语义。

四、长连接的资源管理:不能只建不关

长连接虽好,但空闲连接也占资源(内存、文件描述符、端口)。服务器必须管理它们:

  • keepalive_timeout(空闲超时):连接空闲超过这个时间就主动关闭。设太短,复用率低;设太长,空闲连接堆积占资源。
  • 每连接最大请求数:处理够多请求后主动关,避免单连接长期占用、也利于负载重新分配。
  • 连接数上限:并发长连接太多会耗尽服务器资源,要有上限和 LRU 回收。

客户端侧(浏览器、HTTP 客户端库、连接池)也要管理:连接池复用空闲连接,控制每个 host 的最大连接数、空闲连接存活时间。后端服务之间调用时,用连接池(如数据库连接池、HTTP 连接池)复用连接,是高并发系统的标配。

五、HTTP/1.1 长连接的天花板:还是串行

HTTP/1.1 的长连接解决了「反复建连」,但没解决「一条连接同一时刻只能跑一个请求」。请求必须一问一答,前一个响应没回来,后面的在这条连接上排队——这就是 HTTP 层队头阻塞。

浏览器的应对是给同一域名开多条并发连接(默认约 6 条),相当于 6 条流水线并行。但连接数有限,且每条连接各自要握手、各自慢启动。

HTTP/2 的多路复用才是终极方案:一条连接上用「流」并发多个请求,帧交错传输。于是一条连接顶过去 6 条,既复用了连接、又实现了真正的并发,还省掉多连接的重复握手和慢启动。这也是为什么 HTTP/2 时代不再需要「域名分片」这类 hack。

六、和 TCP Keep-Alive 区分开(重要易混)

面试常挖的坑:HTTP Keep-Alive ≠ TCP Keep-Alive,两者完全不同:

HTTP Keep-AliveTCP Keep-Alive
所在层应用层(HTTP)传输层(TCP)
作用复用连接跑多个 HTTP 请求探测连接对端是否还活着(发保活探针)
触发Connection: keep-alivesocket 选项 SO_KEEPALIVE
目的提升性能,减少建连清理死连接、防止防火墙断掉空闲连接

名字像,作用完全不同。HTTP Keep-Alive 是为了复用,TCP Keep-Alive 是为了保活探测

七、常见误区与追问

考点正确口径
短连接每次请求都新建 TCP/TLS 连接
Keep-Alive复用同一连接发送多个请求
收益减少握手 RTT、慢启动和 CPU 开销
without reuse: TCP handshake + TLS handshake + request
with keep-alive: first handshake once, then many requests
100 requests * 2 RTT handshake saved can be significant

连接复用省的不只是握手时间,还避免频繁慢启动和 TLS 计算开销。

  • 误区:Keep-Alive 永远越久越好。 空闲连接会占用 fd、内存和负载均衡状态,超时时间要按业务调。
  • 误区:HTTP Keep-Alive 等于 TCP keepalive。 前者是 HTTP 连接复用策略,后者是 TCP 层探活机制。
  • 误区:连接复用不会影响负载均衡。 长连接会让请求粘在已有后端,可能影响流量重新分配。
  • 追问:为什么复用能降低延迟? 省掉 TCP 三次握手和 TLS 握手的往返,后续请求可直接发送。
  • 追问:HTTP/2 连接复用更强在哪里? HTTP/2 可在一个连接上多路复用多个并发 stream。
  • 追问:什么时候要主动关闭连接? 服务端限流、灰度切流、后端摘除、空闲过久或连接异常时都需要关闭。

八、加强记忆

HTTP 长连接(Keep-Alive)就是一条 TCP 连接反复用于多个 HTTP 请求,不再每次重新握手、断开。它省的是两笔开销:重复的三次握手/TLS 握手(每次一个多 RTT)+ 反复从头慢启动的低速起步。HTTP/1.1 默认开启,但一条连接同一时刻仍只能一问一答,靠浏览器多开 6 条连接并发;HTTP/2 的多路复用才实现单连接并发多请求。切记别把它和「探测对端死活」的 TCP Keep-Alive 搞混——一个是应用层复用,一个是传输层保活。