长连接与短连接与流水线

长连接 & 短连接 & 流水线

常见的问题”连接管理三兄弟”——短连接、长连接、流水线,本质上都是 HTTP 为了优化性能在不同阶段想出来的招。我们来把这三兄弟的底裤扒干净。

1) 短连接:打完电话就挂,下次再打

HTTP/1.0 的默认模式。 每次 HTTP 请求都要走一遍完整流程:

 
三次握手建立 TCP → 发送请求 → 接收响应 → 四次挥手关闭 TCP
 

比如你打开一个网页,里面有 10 张图片,浏览器就要:

  • 建 10 次 TCP 连接(10 × 3次握手)

  • 断 10 次 TCP 连接(10 × 4次挥手)

通俗比喻: 就像一个话痨朋友,每问你一个问题就打一次电话,问完就挂。下一句又想问,再打一次。一个月下来电话费爆炸。

缺点:

  • 高延迟:每次请求都要经历 TCP 三次握手和慢启动,增加 RTT(Round-Trip Time,往返时间)

  • 资源浪费:频繁创建和销毁 TCP 连接,消耗 CPU、内存和端口资源

  • 拥塞控制失效:每个新连接都从初始拥塞窗口开始,无法充分利用已建立的连接带宽

2) 长连接 Keep-Alive:一个电话聊到底

HTTP/1.1 默认开启长连接。 一条 TCP 连接可以传输多个 HTTP 请求/响应,不用反复”握手-挥手”。

 
三次握手建立 TCP → 请求1 → 响应1 → 请求2 → 响应2 → ... → 空闲超时/主动关闭
 

通俗比喻: 这次聪明了,拨通电话后不挂,一口气把所有问题问完。只要电话两端都没说”再见”,线路就一直通着。

Connection 头部控制:

 
# HTTP/1.1 默认就是长连接,这个头可以省略
 
Connection: keep-alive
 
 
 
# 如果想强制关闭,客户端或服务端任一方发这个
 
Connection: close
 
 
 
# 非标准但广泛支持的保活参数
 
Keep-Alive: timeout=5, max=100
 
# max: 这个连接最多处理 100 个请求
 

关闭方式:

关闭触发条件说明
Connection: close客户端或服务器显式要求关闭
超时(timeout)如 Nginx 的 keepalive_timeout,空闲超过设定时间自动断开
请求数达到上限如 Nginx 的 keepalive_requests,处理完 N 个请求后主动断
网络异常断网、路由器故障等导致 TCP 中断

优点:

  • 减少 TCP 握手/挥手开销,降低延迟

  • 复用连接减少 CPU 和内存消耗

  • 利于 TCP 拥塞控制窗口增长,提高吞吐量

缺点:

  • 队头阻塞(Head-of-Line Blocking):即使连接是持久的,如果第一个请求处理很慢(比如查大数据库),它后面的请求也得排队等着——因为 HTTP/1.1 的响应必须按请求顺序返回。这就是 HTTP 最经典的性能瓶颈。

3) 流水线 Pipelining:不等回复就连着问

建立在长连接之上的”激进优化”。 客户端不用等上一个响应回来,就可以连续发多个请求。但服务器必须按收到请求的顺序依次返回响应。

通俗比喻: 你去快餐店,不用等第一份餐做好,就连着报”汉堡、薯条、可乐”。但后厨的规定是——必须按你报的顺序出餐。结果厨房里汉堡机器坏了,薯条和可乐明明做好了也得在旁边干等着,不能先给你。

工作流程:

 
客户端:发请求1 → 发请求2 → 发请求3(不等回复,连发)
 
服务器:处理1 → 处理2 → 处理3
 
客户端:收响应1 → 收响应2 → 收响应3(必须按序接收)
 

sequenceDiagram

    participant C as 客户端

    participant S as 服务器



    rect rgb(255, 235, 238)

    Note over C,S: 短连接(HTTP/1.0 默认)

    C->>S: TCP 三次握手

    C->>S: 请求1

    S-->>C: 响应1

    C->>S: TCP 四次挥手

    C->>S: TCP 三次握手

    C->>S: 请求2

    S-->>C: 响应2

    C->>S: TCP 四次挥手

    end



    rect rgb(232, 245, 233)

    Note over C,S: 长连接 Keep-Alive(HTTP/1.1 默认)

    C->>S: TCP 三次握手

    C->>S: 请求1

    S-->>C: 响应1

    C->>S: 请求2

    S-->>C: 响应2

    C->>S: 请求3

    S-->>C: 响应3

    C->>S: TCP 四次挥手

    end



    rect rgb(227, 242, 253)

    Note over C,S: 流水线 Pipelining(有队头阻塞)

    C->>S: TCP 三次握手

    C->>S: 请求1

    C->>S: 请求2

    C->>S: 请求3

    Note over S: 处理请求1(很慢...)

    Note over S: 请求2、3已处理完但必须等1

    S-->>C: 响应1(耗时很久)

    S-->>C: 响应2(被阻塞了)

    S-->>C: 响应3(被阻塞了)

    C->>S: TCP 四次挥手

    end

流水线的致命缺陷——队头阻塞的具体表现:

  • 如果请求 1 的响应处理或传输很慢,响应 2 和响应 3 即使准备好了,也必须排队等响应 1 发完

  • 如果中途某个请求失败或连接断开,客户端和服务器需要复杂逻辑判断哪些请求已处理、哪些需要重试

  • 重试非幂等请求(如 POST)可能导致副作用(重复创建数据)

  • 很多代理服务器不完全支持或错误实现流水线,可能导致请求/响应错乱

现状: 由于这些根本性缺陷,现代浏览器和服务器默认禁用流水线。它是一个”理论上美好但实践中失败”的特性。

4) 从流水线到多路复用:HTTP/2 和 HTTP/3 怎么解决的?


graph TD

    subgraph HTTP1.1["HTTP/1.1 长连接"]

        A1["TCP 连接"] --> B1["请求队列(FIFO)"]

        B1 --> C1["响应必须按序"]

        C1 --> D1["队头阻塞⚠️"]

    end



    subgraph HTTP2["HTTP/2 多路复用"]

        A2["一个 TCP 连接"] --> B2["二进制分帧层"]

        B2 --> C2["Stream 1: 请求A/响应A<br/>Stream 3: 请求B/响应B<br/>Stream 5: 请求C/响应C"]

        C2 --> D2["帧交错发送<br/>应用层队头阻塞已解决✅"]

        D2 --> E2["但 TCP 丢包<br/>仍会导致传输层队头阻塞⚠️"]

    end



    subgraph HTTP3["HTTP/3 QUIC"]

        A3["QUIC(UDP)"] --> B3["每个流独立处理"]

        B3 --> C3["丢包只影响该流<br/>其他流不受影响"]

        C3 --> D3["彻底解决队头阻塞✅"]

    end



    HTTP1.1 --> HTTP2 --> HTTP3

HTTP/2 的核心改进:

  • 二进制分帧:把 HTTP 消息拆成独立的”帧”(HEADERS 帧、DATA 帧等),每个请求/响应流分配唯一 Stream ID

  • 多路复用:不同 Stream 的帧可以交错发送,不按顺序,接收端按 Stream ID 重组

  • 这解决了 HTTP 应用层的队头阻塞,但 TCP 传输层的队头阻塞 仍然存在——一个 TCP 包丢了,所有 Stream 都得等重传

HTTP/3 的终极方案:

  • 把传输层从 TCP 换成 QUIC(基于 UDP)

  • 在 QUIC 层面实现多路复用,每个流独立处理

  • 丢包只影响该包所属的流,其他流完全不受影响

  • 还支持 0-RTT 连接建立,进一步减少延迟

5) 对比总结

特性HTTP/1.0 短连接HTTP/1.1 长连接HTTP/1.1 流水线HTTP/2 多路复用HTTP/3 QUIC
连接模式请求-响应-关闭多请求复用同一连接多请求复用同一连接单连接多路复用单连接多路复用
请求发送等上一个响应完成通常等上一个响应完成无需等待,连续发送随时交错发送帧随时交错发送帧
响应顺序自然顺序必须按请求顺序必须按请求顺序帧可交错,按流ID重组帧可交错,按流ID重组
队头阻塞无(连接独立)存在(应用层)严重(应用层)存在(传输层TCP)基本解决
默认状态历史协议默认启用广泛禁用主流新兴协议

延伸追问

Q: 为什么流水线没能解决队头阻塞?

因为流水线虽然可以并发发送请求,但 HTTP/1.1 协议规定响应必须严格按照请求的发送顺序返回。这是一个”串行响应”模型——如果队头请求的响应处理慢了(比如查了一个慢 SQL),后面已经处理好的响应也只能干等着。这不是实现问题,是协议设计层面的限制

Q: HTTP/2 的多路复用和 HTTP/1.1 的流水线有什么区别?

核心区别:流水线是”并发发送,串行响应”;多路复用是”并发发送,乱序响应,按 Stream ID 重组”。

打个比方:

  • 流水线 = 你去食堂打饭,报了一串菜名,但阿姨必须按你报的顺序打菜,红烧肉没做好,后面的番茄炒蛋也拿不到

  • HTTP/2 多路复用 = 食堂改成自助餐,每个菜一个窗口,你同时排 3 队,哪个先好就先拿哪个,最后自己按菜单编号拼成一桌

Q: HTTP/2 完全解决了队头阻塞吗?

没有! HTTP/2 只解决了 应用层(HTTP 协议层)的队头阻塞。但是 HTTP/2 所有流都跑在同一条 TCP 连接上,TCP 本身是有序传输的——丢了一个 TCP 包,后续所有包都得等重传,所有 Stream 全被卡住。这叫 TCP 传输层的队头阻塞

HTTP/3 改用基于 UDP 的 QUIC 协议,每个流独立,丢包只影响自己的流,这才算基本解决。


速记卡(面试闪卡)

Q1:一句话讲清「长连接与短连接与流水线」到底是什么?

A:短连接每次请求都建断TCP,长连接Keep-Alive复用一条TCP传多个请求,流水线Pipelining在长连接上连发请求——三者都是HTTP为省握手开销想出的连接管理招。

Q2:短连接:打完就挂 —— 怎么理解?

A:短连接(Short Connection)就像话痨朋友,每问一个问题就打一次电话、问完立刻挂,下句再打一次——HTTP/1.0 每次请求都走完整三次握手加四次挥手,电话费(延迟和端口)爆炸。

Q3:长连接 Keep-Alive:一个电话聊到底 —— 怎么理解?

A:长连接(Persistent Connection / Keep-Alive)是 HTTP/1.1 默认模式:拨通后不挂,一口气把所有请求传完。省掉反复握手,但首个请求慢会卡住后面(队头阻塞 Head-of-Line Blocking)。

Q4:流水线 Pipelining:不等回复连连问 —— 怎么理解?

A:流水线(HTTP Pipelining)在长连接基础上更激进:客户端不等响应就连发多个请求,服务器必须按请求顺序依次回——像快餐店连报”汉堡薯条可乐”,但前面卡住后面全得等。

Q5:队头阻塞 HOL 是核心痛点 —— 怎么理解?

A:队头阻塞(Head-of-Line Blocking, HOL)是 HTTP/1.x 的经典瓶颈:因响应必须按请求顺序返回,一个慢请求会堵死后面所有请求,逼出 HTTP/2 多路复用和 HTTP/3 换 UDP。

Q6:核心速记主线有哪些?

  • 短连接:HTTP/1.0 默认,一请求一建断,延迟高

  • 长连接:HTTP/1.1 默认 Keep-Alive,复用 TCP 省握手

  • 流水线:长连接上连发请求,按序响应,仍受 HOL 困

  • HOL 队头阻塞逼出 HTTP/2 多路复用 / HTTP/3 QUIC

口诀

A:短连每问挂一次

长连一话聊到底

流水连问按序回

头阻逼出二三版

相关链接