前端大文件上传的另一种提速思路
<h1 data-id="heading-0">🧑💻 写在开头</h1><p>点赞 + 收藏 === 学会🤣🤣🤣</p>
<div>
<div>
<p>最近在重构项目里的大文件上传模块,本想着按常规方案实现:File API 切片、计算 Hash、封装一个带并发限制(通常习惯性设为 6)的请求池,最后调个 Merge 接口收尾。</p>
<p>这套方案可以说是前端圈处理大文件的标配了。但看着 Network 面板里稳步推进的进度条,我突然意识到一个经常被忽略的细节:<strong>平时我们习惯性设置的“6 个并发请求”,其实是 HTTP/1.1 时代的经验产物。</strong></p>
<p>在 HTTP/1.1 中,由于协议本身的限制,一个 TCP 连接在同一时刻只能处理一个请求。为了避免某个慢请求把后面的请求全堵死(队头阻塞),浏览器不得不采取一种妥协的策略:<strong>针对同一个域名,建立多个独立的 TCP 连接</strong>(大部分浏览器限制为 6 个)。<br>
这就像是超市里开了 6 个结账通道,虽然不多,但在物理上是实打实并行的。</p>
<p>根据现代浏览器的机制,对于同一个域名,HTTP/1.1 确实会建立最多 6 个左右的 TCP 连接来实现物理层面的并行。但如今我们的生产环境几乎都已经全面拥抱了 HTTP/2。</p>
<p>根据 RFC 7540 规范,HTTP/2 的核心特性之一就是<strong>单连接多路复用</strong>。这就意味着,浏览器面对同一个域名,通常只会建立 <strong>1 个 TCP 连接</strong>。</p>
<p>这就引发了我的一个思考:</p>
<p>虽然 HTTP/2 的多路复用通过二进制分帧解决了 HTTP/1.1 必须等待上一个请求响应才能发下一个的痛点,省去了大量的排队等待时间,但只要这些分片同属一个域名,底层就依然只有一条 TCP 连接。<strong>在物理传输层面上,这些并发切片的数据依然是串行发送的。</strong></p>
<p>那么,如果我们能像 HTTP/1.1 时代那样,打破单条 TCP 连接的束缚,逼着浏览器多开几条物理 TCP 通道,是不是就能在 HTTP/2 的基础上实现真正的物理并行,从而进一步提升上传速度?</p>
<p>既然浏览器是按“域名”来复用 TCP 连接的,顺着这个思路,我周末写了个 Demo,尝试用多域名分片来验证这个猜想,还真拿到了一组直观的对比数据。</p>
<h3 data-id="heading-0">搭建多域名对照组实验</h3>
<p>为了验证多 TCP 通道是否能带来上传速度的提升,我们需要在本地搭一个对照组。本地跑这个实验只有一个前置难点:<strong>HTTP/2 的开启条件。</strong></p>
<p>目前所有主流浏览器(Chrome, Firefox, Safari)在实现 HTTP/2 时,都强制要求基于 TLS(HTTPS),通过 ALPN (Application-Layer Protocol Negotiation) 扩展来协商协议。因此,本地跑 HTTP/2 必须配置 SSL 证书。</p>
<p><strong>1. 配置多域名与证书</strong></p>
<p>首先修改一下系统的 <code>/etc/hosts</code> 文件,映射几个指向本地的别名域名:</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">127.0.0.1 u1.local.com
127.0.0.1 u2.local.com
127.0.0.1 u6.local.com</pre>
</div>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260427110917901-1401293657.png" alt="ScreenShot_2026-04-27_110905_477" loading="lazy"></p>
<div>
<div>
<p>然后,使用 mkcert 在本地给这几个域名一键签发受信任的 SSL 证书,留给后端服务使用。</p>
<p><strong>2. 极简的 Node.js 后端</strong></p>
<p>后端不需要复杂的业务逻辑,起个 Express 服务,挂载刚刚签发的证书,写个接口专门接收切片即可。这里唯一要注意的是 cors 跨域配置,因为前端接下来会跨好几个子域名来发请求。</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">const https = require('https');
const express = require('express');
const cors = require('cors');
const app = express();
// 允许携带 credentials 以及来自不同子域名的跨域请求
app.use(cors({
origin: (origin, callback) => callback(null, true),
credentials: true
}));
app.post('/upload', (req, res) => {
// 省略切片落盘逻辑...
});
// 使用 mkcert 生成的证书启动 HTTPS 服务
https.createServer(sslOptions, app).listen(443);</pre>
</div>
<p>3. 前端的动态网关调度</p>
<p>平时我们写上传,目标 URL 通常写死为一个。现在我们需要维护一个“域名池”。在遍历分片数组时,通过简单的取模算法,把不同的分片请求均匀地分配给这些不同的子域名。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">// 我们预设的上传域名池
const SHARDING_DOMAINS =[
'https://u1.local.com',
'https://u2.local.com',
'https://u6.local.com'
];
async function uploadChunks(chunks) {
const uploadTasks = chunks.map((chunk, index) => {
// 轮询分配域名
const targetDomain = SHARDING_DOMAINS;
const url = `${targetDomain}/upload`;
const formData = new FormData();
formData.append('chunk', chunk.file);
return fetch(url, { method: 'POST', body: formData });
});
// 使用并发控制函数(这里省略 p-limit 的实现),最大并发保持 6
await asyncPool(6, uploadTasks);
}</pre>
</div>
<h3 data-id="heading-1">直观的数据对比</h3>
<p>实验准备就绪。我用一个约 1.5G 的测试文件,在同样的本地网络环境下,分别跑了“单域名常规上传”和“多域名分片上传”。</p>
<p>直接看 Chrome Network 面板的截图对比:</p>
<div>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260427111002122-103472211.png" alt="ScreenShot_2026-04-27_110956_607" loading="lazy"></p>
<div>
<div>
<p>这张图里验证了几个关键的细节:</p>
<ol>
<li><strong>左侧(策略 A:单域名):</strong><br>
耗时 3.58s,吞吐量大概在 420.35 MB/s。<br>
注意看下方请求列表中 u1.local.com 对应的 <strong>连接 ID (Connection ID)</strong> ,所有的分片请求,其 ID 完全一致(均为 2081087)。这在工程实际上证明了,哪怕你发起了并发请求,HTTP/2 依然尽职尽责地把它们全塞进了这一条 TCP 隧道里串行发送。</li>
</ol></div>
</div>
</div>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260427111030278-11029079.png" alt="ScreenShot_2026-04-27_111018_167" loading="lazy"></p>
<div>
<div>耗时缩短到了 2.44s,吞吐量达到了 616.82 MB/s,速度提升了将近 <strong>46%</strong> 。<br>
再看底下的请求列表,发往 u1.local.com、u2.local.com 和 u6.local.com 的请求,分别拿到了 <strong>三个独立的 TCP 连接 ID</strong>(2081087、2082684、2081775)。</div>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202604/2149129-20260427111042936-1309385556.png" alt="ScreenShot_2026-04-27_111025_223" loading="lazy"></p>
<div>
<div>
<p>事实证明,通过我们在前端引入多域名策略,成功越过了浏览器针对 HTTP/2 的单连接复用机制,在物理层面上拓宽了上传的整体带宽,实现了真正的物理并行传输。</p>
<p>以上实验的完整前后端代码已经提交到了 GitHub,代码比较精简,主要为了提供一个验证思路。欢迎大家在本地跑跑看,或者交流不同的见解。</p>
<p>🔗 <strong>前端 Demo 源码:</strong> large-file-upload-demo-frontend<br>
🔗 <strong>后端 Demo 源码:</strong> large-file-upload-demo-backend</p>
</div>
</div>
<div>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
</div>
<p><em><img src="https://img2024.cnblogs.com/blog/2149129/202501/2149129-20250122165814748-630765389.png" alt="" loading="lazy"></em></p>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/19935812
頁:
[1]