我们的网站中包含音频、MV视频等大文件的上传,如果不作特殊处理,将遇到以下问题:
- 网络中断、程序异常退出等问题导致文件上传失败,从而不得不全部重新上传
- 同一文件被不同用户反复上传,白白占用网络和服务器存储资源
因此,需要一个针对大文件上传的方案来解决上述问题。
传统的大文件上传都是在客户端先完成所有的分片,然后计算每个分片和完整的文件hash,再使用hash和服务器换取当前文件的信息。
由于hash的计算是CPU密集型的操作,这样一来就会导致长时间的客户端阻塞,虽然可以使用Web Worker来加速hash的计算,但经过测试,即便是使用了多线程,某些超大文件比如上了10个G的文件,在配置不太好的客户机上计算时长可以超过30秒,这是无法接受的。
因此对上传流程进行了优化,假定大部分文件上传都是一个新文件上传,于是在流程上,允许用户在获得文件完整hash之前直接上传分片,这样一来,几乎可以做到零延时的上传,等到文件整体hash计算出来之后,再向服务器补充hash数据。
基于这样的流程设计了一套标准化的文件上传协议
协议主要包含四个通信标准:
创建文件协议: 前端使用HEAD请求向服务器提交文件基本信息,换取上传唯一token,后续的请求必须携带此token
hash校验协议: 前端把某个分片hash或者是整个文件hash发送给服务器,得到分片和文件的状态信息
分片上传协议: 前端将分片的二进制数据发送到服务器存储
分片合并协议: 前端提示服务器可以完成分片合并了
服务器最大的挑战就是如何保证每个分片的唯一性,这种唯一性即包含存储的唯一性,也包含传输的唯一性。
存储的唯一性保证了分片不会重复保存,避免了数据的冗余。
传输的唯一性保证了分片不会重复上传,避免了通信的冗余。
要保证分片不会重复保存,就必须让分片和文件解耦,分片是分片,文件是文件,分片独立保存,不从属于任何文件,文件独立记录,按照顺序依次指向不同分片,这样一来,哪怕出现两个不同文件拥有相同分片的场景,也不会在服务器出现重复存储的问题,因为分片是独立于文件的。
而要保证分片不会重复上传,就必须保证分片永不删除,如果在合并文件后删除了分片,就会导致下一次有相同分片上传时服务器找不到对应的分片文件,就必须重复上传,因此分片永不删除。
最后就是合并分片的逻辑,考虑到如果真正的把分片文件合并成一个大文件,大文件的所有数据实际上都是冗余的,而且整个合并过程极其耗时,因此做了这样的处理。
当收到合并请求时,服务器其实仅仅做一些简单的校验即可,比如文件大小、分片数量等校验,而不进行真正的合并,仅在数据库mongodb中更新该文件的状态并生成文件访问的url地址即可。
等到用户真正访问文件时,我根据数据库中对应文件的分片记录,使用文件流依次读取分片数据,用流管道直接响应给客户端即可。
这样一来整个合并效率和文件访问效率都极高,同时服务器的存储不会有任何冗余。
client层安装
npm i @momosemitsuki/upload-client # npm
yarn add @momosemitsuki/upload-client # yarn
pnpm add @momosemitsuki/upload-client # pnpmserver层安装
npm i @momosemitsuki/upload-server # npm
yarn add @momosemitsuki/upload-server # yarn
pnpm add @momosemitsuki/upload-server # pnpmupload-server主要提供中间件来处理请求
目前支持
- express
- koa
当然也可以自行导入 service 函数书写中间件处理