云计算百科
云计算领域专业知识百科平台

为什么上传进度条总卡在 99%?后端服务器正在经历一场“IO风暴”

为什么上传进度条总卡在 99%?后端服务器正在经历一场“IO风暴”

🐘 理想中的上传:扔进去就行

在产品经理和用户的眼里,文件上传就是把本地的文件“复制粘贴”到服务器上:

动作代码行数 (理想状态)描述
前端选择 1 行 <input type="file">
发起上传 1 行 FormData.append('file', file); axios.post(url, data)
后端接收 1 行 file.saveTo(disk)

总计:3 行。
如果文件只有 1MB(比如头像),这确实是真的。
但当老板说:“我们要支持 2GB 的视频上传,还要支持断网续传,还要支持秒传”时,这 3 行代码就变成了一个庞大的分布式文件处理系统。


💥 第一关:大文件的“暴食症” (OOM & Timeout)

如果你敢直接把一个 2GB 的文件塞进 HTTP POST 请求里发给后端:

  • 浏览器崩溃: 前端读取 2GB 文件到内存,浏览器直接卡死。
  • 网关拦截: Nginx 默认限制 client_max_body_size 1m。你还没进门就被保安拦住了:“包裹太大,禁止入内!”
  • 后端内存溢出: 假设后端收到了,Java/Python 试图把这 2GB 加载到内存里处理。OOM (Out Of Memory),服务宕机。
  • 超时断连: 传了 10 分钟,网络稍微抖了一下,连接断了。前功尽弃,从头再来。 用户砸键盘。
  • 结论: 大文件不能“吞”,只能“切”。


    🔪 第二关:千刀万剐 (分片上传 Chunking)

    为了解决“太大”的问题,程序员必须化身为外科医生,把文件切成无数个小块。

    前端逻辑:
    利用 Blob.slice() 方法,把 2GB 的文件切成 2000 个 1MB 的小切片。
    然后起一个循环,发 2000 个 HTTP 请求:

    • “这是第 1 片”
    • “这是第 2 片”

    后端逻辑:
    后端变成了收废品站。
    我不存完整文件了,我先在一个临时目录里堆放这 2000 个碎片文件:file_abc_part_1, file_abc_part_2…

    代码山的代价:

    • 你要管理切片顺序。
    • 你要处理并发上传(浏览器限制同域名只能有 6 个 TCP 连接,你得写个队列)。
    • 你要处理失败重试(第 500 片传失败了,只能重传这一片,不能重传整个文件)。

    🧩 第三关:拼图地狱 (Merge)

    所有切片都传完了,文件还在服务器的临时目录里躺着,是散的。
    前端发来一个指令:“传完了,合并吧!”

    后端噩梦开始:

  • IO 风暴: 你的代码要打开 2000 个小文件,读取内容,写入到一个大文件里。这时候磁盘 IO 直接飙升,服务器卡顿。
  • 文件锁: 万一用户手贱点了两次“合并”,你启动了两个线程同时去写同一个文件,文件内容就乱套了。
  • 磁盘空间不足: 你合并时,磁盘上同时存在“2GB 的碎片”和“正在生成的 2GB 大文件”。你需要 2 倍 的磁盘空间。
  • 校验 (MD5): 万一第 1999 个碎片在传输中损坏了一个比特?合并出来的视频就是坏的。你必须在合并后计算全文件的 MD5——这又是一次惊人的 CPU 和 IO 消耗。

  • ⏸️ 第四关:断点续传的“记忆术”

    用户传到 50%(第 1000 片),网断了。
    半小时后,网好了。用户重新选择了文件。

    如果从头传,用户会杀人。
    系统必须有记忆。

    交互流程:

  • 握手 (Pre-check): 上传前,前端先算一个文件的“指纹”(MD5),问后端:“大哥,这个文件我传过吗?”
  • 后端查询: 后端去临时目录看一眼:“哦,这个指纹的文件,我已经收到了 1-1000 片,你从 1001 片开始传吧。”
  • 续传: 前端跳过前 1000 片,继续发送。
  • 坑点:
    前端算 MD5 很慢!
    让浏览器去读 2GB 文件算哈希值,页面会卡死。
    优化技巧: 抽样计算(只算文件头、文件尾和中间几块),牺牲一点准确性换取速度。


    ⚡ 第五关:秒传 (Instant Upload) 的魔术

    这是让用户觉得“哇,你们网速好快”的诈骗功能。

    场景:
    用户 A 上传了一部电影《复仇者联盟》,传了 1 小时。服务器上有了这份文件。
    用户 B 也上传同一部电影。
    前端一算 MD5,发给后端。
    后端一看:“嘿!这文件我库里有啊!”
    后端直接返回:“上传成功!”(耗时 0.1 秒)。

    实际上: 用户 B 的文件根本没上传,后端只是在数据库里给用户 B 的文件列表里,加了一条引用 (Link),指向了用户 A 传的那份文件。

    风险:
    哈希碰撞 (Hash Collision)。
    虽然概率极低,但理论上可能存在两个不同的文件 MD5 一样。
    如果发生了,用户 B 上传了自己的自拍,结果下载下来变成了《复仇者联盟》。
    为了防这个,通常要用 MD5 + SHA1 双重校验,或者对比文件长度。


    🧹 第六关:垃圾清理 (GC)

    场景:
    每天有 1000 个人上传文件。
    其中 100 个人传了一半就走了,再也没回来。
    服务器的临时目录里留下了几十万个永远不会被合并的碎片文件。
    随着时间推移,磁盘爆满。

    防御代码:
    你必须写一个定时任务(Cron Job),每天凌晨爬起来巡逻:
    “这些碎片是谁的?都三天了还没合并?删了!”


    💡 总结:这不是上传,这是分布式工程

    现在你明白了,那个简单的 <input type="file"> 背后,其实跑着一个复杂的分布式系统:

  • 切片(为了避开内存和网络限制)。
  • 并发(为了速度)。
  • 哈希计算(为了秒传和校验)。
  • 状态机(为了续传)。
  • IO 调度(为了合并)。
  • 垃圾回收(为了磁盘清洁)。
  • 所以,当下一次你看到进度条卡在 99% 时,请不要怪网速。
    那通常是后端正在拼命地把几千个碎片往一起缝合,CPU 正在冒烟,磁盘正在尖叫。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 为什么上传进度条总卡在 99%?后端服务器正在经历一场“IO风暴”
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!