浏览器对同一域名的fetch并发请求数有硬限制(Chrome为6个),本质是TCP长连接数限制,不可绕过;需手动控制主动发起的并发节奏,避免pending堆积、内存上涨及服务端429。

浏览器对同一域名的 fetch 并发请求数有硬性限制(Chrome 为 6 个),这本质是 TCP 长连接数限制,不是你能绕过的配置项。当批量发起几十甚至上百个 fetch 请求时,真正卡住你的往往不是“请求发不出去”,而是连接排队、响应延迟飙升、内存持续上涨,甚至触发服务端 429 —— 这些现象背后,就是 TCP 连接层的阻塞在起作用。
识别 TCP 连接阻塞的典型表现
不用等页面完全卡死,这几个信号一出现,基本可以判定是并发连接耗尽导致的阻塞:
- Network 面板里大量请求状态长期停留在 “pending”:尤其同域名下,超过 6 个请求同时 pending,且不转为 “waiting for response” 或 “receiving data”
-
DevTools 的 Connection 列显示重复 TCP 连接复用失败:同一域名下出现大量
tcp-connection-xxx(而非reuse),说明浏览器被迫新建连接但已达上限 -
Recv_Q 值异常升高(需服务端配合):若你可控后端,用
ss -i或netstat -s查看对应连接的Recv-Q持续 >1MB 且不下降,表明数据已到内核缓冲区但应用层未读取,连接被“堵住” -
页面内存持续上涨,且 Memory 面板中 JS Heap 里存在大量未释放的
File、FormData或Blob引用:这不是网络层问题,但会加剧阻塞感知——大文件上传未清理引用,会让 GC 无法回收,拖慢整个事件循环
浏览器 TCP 并发限制的本质与算法逻辑
这个“6 个”不是魔法数字,而是浏览器对单域名并发 TCP 连接数的默认策略,底层基于 HTTP/1.1 长连接机制:
- 每个 TCP 连接可复用处理多个 HTTP 请求(pipelining 已废弃,实际是串行 request/response)
- 浏览器为每个目标域名(如
api.example.com)最多建立 6 个独立 TCP 连接;第 7 个请求必须等待前 6 个中任一连接空闲或关闭 - 该限制按「源域名」隔离:访问
a.example.com和b.example.com各有 6 个连接额度(即“域名切片”有效) - 它不区分 fetch / xhr / 图片 / script 加载——所有同源资源共用这 6 个连接槽位
如何验证是否真被 TCP 连接数卡住
别只看前端日志,用三步交叉验证:
-
前端抓包确认:在 DevTools Network 中过滤
Other类型,查看是否有大量tcp-connect时间远长于ssl或request,说明连接建立本身就在排队 - 服务端日志比对:检查 access log 中同一秒内到达的请求数是否远少于你前端发出的数量(比如发了 50 个,服务端只收到 6–8 个/秒),其余都在客户端排队
-
本地复现 + netstat 辅助:用 Netty 搭一个简易 HTTP Server(如知识库中示例),前端固定请求该地址并逐步增加并发量,执行
netstat -an | grep :your_port | wc -l,观察 ESTABLISHED 连接数是否稳定卡在 6
真正要控制的不是浏览器限制,而是你主动发起的并发节奏
浏览器的 6 连接限制改不了,但你可以决定“每秒主动 push 几个 fetch”。关键不是压满 6 个,而是让它们高效流动:
- 用
p-limit控制任务队列,例如const limit = pLimit(3),确保最多 3 个 fetch 同时 pending + running,留出余量给图片、脚本等其他资源 - 避免“全量切片式并发”(如一次
Promise.all(urls.slice(0,6))),而应采用“动态接力”:每个 fetch 完成后立即从队列取下一个,保持连接利用率,降低平均延迟 - 对大文件上传,fetch 必须传
{ signal: new AbortController().signal },且上传结束立刻置formData = null,否则 File 对象长期驻留内存,间接拖垮 TCP 调度能力


















