4. HTTP/1.1 并发连接:浏览器并发连接数限制、Keep-Alive 机制与 Pipeline 请求分析

聊到 HTTP/1.1 的并发,很多人第一反应就是“浏览器有连接数限制”。没错,但这只是冰山一角。真正让我在项目里栽过跟头的,其实是 Keep-Alive 的“假持久”和 Pipeline 的“半残废”设计。今天咱们就掰开揉碎了,把这三个东西彻底讲透。

核心结论先行:HTTP/1.1 的并发能力,本质上是在“单连接串行”和“多连接并行”之间找平衡。Keep-Alive 减少了连接开销,但 Pipeline 因为队头阻塞问题,实际落地效果很差。

4.1 浏览器并发连接数限制:为什么是 6 个?

你打开 Chrome 的开发者工具,随便刷个页面,看看 Network 面板。你会发现同一个域名下,最多同时建立 6 个 TCP 连接。这不是巧合,这是规范。

RFC 2616 里其实没写死这个数字,它只是建议客户端“不要使用超过 2 个连接”。但后来各家浏览器发现,2 个根本不够用。我记得 2010 年左右,Firefox 率先把默认值提到了 6,Chrome 和 Safari 也跟进了。现在这个 6 就成了事实标准。

为什么会是 6?说白了,这是经验值。太少,页面加载慢;太多,服务器扛不住,而且会触发 TCP 的拥塞控制,反而降低整体吞吐。我当年调优过一个电商首页,把连接数从 6 改到 10,结果首屏时间反而增加了 200ms。嗯,这就是典型的“好心办坏事”。

浏览器 每个域名最大并发连接数 备注
Chrome 6 HTTP/1.1 默认值
Firefox 6 可通过 network.http.max-connections 调整
Safari 6 macOS/iOS 一致
IE/Edge (旧版) 6 IE 8 及以后版本
HTTP/2 1 多路复用,单连接即可

抓包小技巧:用 Wireshark 过滤 tcp.port == 80,然后观察同一时刻的 SYN 包数量。如果同一个源 IP 到目标 IP 的 SYN 包超过 6 个,说明浏览器可能绕过了限制(比如用了域名分片)。

4.2 Keep-Alive 机制:持久连接的真相

HTTP/1.0 时代,每次请求都要新建 TCP 连接,三次握手四次挥手,光握手就浪费大几十毫秒。Keep-Alive 的出现,就是为了解决这个问题。

它的原理很简单:请求完成后,不关闭 TCP 连接,留着给下一个请求复用。你可以在响应头里看到 Connection: keep-alive,或者 Keep-Alive: timeout=5, max=100。前者告诉客户端“连接别断”,后者告诉客户端“最多等 5 秒,最多复用 100 次”。

但这里有个坑。我曾经排查过一个线上问题:用户反馈页面偶尔卡死,抓包发现 Keep-Alive 连接被服务器端主动关闭了,但浏览器不知道,还在往这个“死连接”上发请求。结果就是请求超时,浏览器再重新建连,一来一回多了 1 秒多。

避坑指南:我曾经在 Nginx 配置里把 keepalive_timeout 设成了 65 秒,结果和客户端的 60 秒超时对不上。服务器端先关了连接,客户端还在用。后来统一改成 60 秒,问题解决。记住,服务端和客户端的超时时间要匹配,否则就是“你关你的,我发我的”。

Keep-Alive 的抓包特征很明显:看 TCP 流,第一个请求结束后,没有 FIN 或 RST 包,紧接着就是第二个请求的 HTTP 数据。你可以在 Wireshark 里用 http.request 过滤,然后看 Time 列,如果两个请求之间的时间差很小(毫秒级),基本就是复用了同一个连接。

4.3 Pipeline 请求分析:理想很丰满,现实很骨感

Pipeline 是 HTTP/1.1 提出的一个“增强”方案。它的想法是:客户端不用等上一个响应回来,就可以连续发多个请求。服务器按顺序处理,按顺序返回。

你想想看,这本来是个好设计。但问题出在“队头阻塞”(Head-of-Line Blocking)上。如果第一个请求的处理时间很长,后面的请求即使已经到达服务器,也得排队等着。更糟糕的是,如果第一个请求的响应丢了,客户端就不知道后面的响应到底对应哪个请求了。

我实际测试过,在 Chrome 里默认是禁用 Pipeline 的。为什么?因为很多代理服务器和中间件对 Pipeline 的支持有问题。我曾经在客户现场抓包,发现一个反向代理把 Pipeline 请求的响应顺序搞乱了,导致前端 JS 解析 JSON 失败,页面白屏。查了三天,最后发现是代理的 HTTP 解析器有 bug。

Pipeline 的现状:大部分现代浏览器已经默认关闭 Pipeline。HTTP/2 的多路复用才是正解。如果你还在用 HTTP/1.1,老老实实靠多连接并发,别折腾 Pipeline。

抓包时怎么识别 Pipeline?看 TCP 流的发送方向。如果客户端连续发出多个 HTTP 请求(没有等待响应),那就是 Pipeline。在 Wireshark 里,你可以看到类似这样的序列:

客户端 -> 服务器: GET /1.html HTTP/1.1
客户端 -> 服务器: GET /2.html HTTP/1.1
客户端 -> 服务器: GET /3.html HTTP/1.1
服务器 -> 客户端: HTTP/1.1 200 OK (响应 /1.html)
服务器 -> 客户端: HTTP/1.1 200 OK (响应 /2.html)
服务器 -> 客户端: HTTP/1.1 200 OK (响应 /3.html)

注意看,客户端连续发了三个 GET,中间没有等待。这就是 Pipeline 的典型特征。但现实中,你很少能看到这种场景,因为浏览器基本不用了。

4.4 三者关系:一张图看懂

下面这张图,把浏览器并发连接、Keep-Alive 和 Pipeline 的关系画清楚了。我建议你保存下来,面试或者排障时直接拿出来用。

HTTP/1.1 并发机制关系图 浏览器 连接池 (最多6个) TCP 连接 #1 (Keep-Alive) TCP 连接 #2 (Keep-Alive) TCP 连接 #3 (Keep-Alive) TCP 连接 #4 (Keep-Alive) TCP 连接 #5 (Keep-Alive) TCP 连接 #6 (Keep-Alive) Pipeline 请求队列 (实际已废弃) 关键说明 • 浏览器最多开6个连接 • 每个连接复用 Keep-Alive • Pipeline 已基本废弃 • HTTP/2 用多路复用替代

4.5 实战抓包:三步定位并发问题

光说不练假把式。我给你一个实战步骤,下次遇到页面加载慢,直接套用。

  1. 第一步:看连接数。在 Wireshark 里过滤 tcp.port == 80 && tcp.flags.syn == 1,统计同一时刻的 SYN 包数量。如果超过 6,说明有域名分片或者 HTTP/2 混用。
  2. 第二步:看 Keep-Alive。过滤 http.response,找到 Connection 头。如果服务器返回 Connection: close,说明 Keep-Alive 没生效。检查服务器配置。
  3. 第三步:看 Pipeline。过滤 http.request,看请求之间的时间间隔。如果连续多个请求的时间戳几乎相同(毫秒级),那就是 Pipeline。但大概率你看不到,因为浏览器不用了。

我的习惯:每次调优 HTTP/1.1 并发,我都会先抓一个“基准包”,然后修改配置,再抓一个“对比包”。两个包放在一起看,效果立竿见影。别凭感觉调,数据说话。

好了,HTTP/1.1 的并发机制就聊到这儿。记住三个关键词:6 个连接、Keep-Alive 复用、Pipeline 已死。下一层,咱们该看看 HTTP/2 是怎么用多路复用彻底解决这些问题的。