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 的关系画清楚了。我建议你保存下来,面试或者排障时直接拿出来用。
4.5 实战抓包:三步定位并发问题
光说不练假把式。我给你一个实战步骤,下次遇到页面加载慢,直接套用。
- 第一步:看连接数。在 Wireshark 里过滤
tcp.port == 80 && tcp.flags.syn == 1,统计同一时刻的 SYN 包数量。如果超过 6,说明有域名分片或者 HTTP/2 混用。 - 第二步:看 Keep-Alive。过滤
http.response,找到Connection头。如果服务器返回Connection: close,说明 Keep-Alive 没生效。检查服务器配置。 - 第三步:看 Pipeline。过滤
http.request,看请求之间的时间间隔。如果连续多个请求的时间戳几乎相同(毫秒级),那就是 Pipeline。但大概率你看不到,因为浏览器不用了。
我的习惯:每次调优 HTTP/1.1 并发,我都会先抓一个“基准包”,然后修改配置,再抓一个“对比包”。两个包放在一起看,效果立竿见影。别凭感觉调,数据说话。
好了,HTTP/1.1 的并发机制就聊到这儿。记住三个关键词:6 个连接、Keep-Alive 复用、Pipeline 已死。下一层,咱们该看看 HTTP/2 是怎么用多路复用彻底解决这些问题的。