APP下载

基于自适应分片的大文件快速上传①

2022-08-04周煜莹崔岩松王丹志陈科良

计算机系统应用 2022年7期
关键词:服务端表单线程

周煜莹,崔岩松,王丹志,陈科良

(北京邮电大学 电子工程学院,北京 100876)

在计算机网络技术迅猛发展的今天,文件的上传是一个重要的应用交互场景. 对于普通的图片或word文档等几十KB 或者几MB 的文件上传,使用Web 的组件即可完成流畅的上传功能. 通常的文件上传是一次性获取整个文件上传,传输过程简单: 第1 步获取本地文件,第2 步将文件转换成字节传输,第3 步后端接收按顺序接收字节到内存中,最后将接收完的字节保存为文件[1]. 但在大文件传输的应用中,例如在邮箱管理系统中上传大容量的资源压缩包,在网络视频发布系统中上传视频文件,在线制作电子相册时需要上传高清图片,网络硬盘服务系统,局域网文件交换系统等[2]. 这些业务场景下的大文件传输很容易占据较大的带宽资源,造成网页访问速度降低,也可能导致后端服务器响应超时,前端页面长时间无响应,甚至卡顿而导致页面崩溃. 即便能够上传成功,用户需要较长的等待时间,在此期间不能刷新页面,只能等待请求完成. 这些问题严重降低了用户体验,因而大文件上传一直是Web 应用系统的一大痛点.

针对以上问题,本文基于Node.js 边读边写的流模式传输,采用HTML5 的File API 对上传的大文件进行分片处理,通过上传速率动态调整分片大小,同时充分利用带宽,结合多并发上传进一步缩短上传时间,在服务端检验所有分片文件上传完整后,再进行文件的合并,有效的提高了大文件的上传速率,减少了用户的等待时长.

1 文件上传的常用方式

目前,基于HTTP 协议的文件上传方式有以下几种:

(1)表单上传

这是Web 开发中最常见的上传方式,使用Form表单的input[type=“input”]打开文件选择界面,通过POST 方法向指定资源提交表单数据[3]. 上传的文件使用multipart 格式,编码类型为“multipart/form-data”[4].

(2)无刷新的Ajax 上传

区别于表单上传,使用Ajax 的异步上传,在提交表单数据不需要刷新和跳转页面. 提交数据时,可以使用FormData 对象模拟表单提交,发送表单的二进制文件内容,通过XMLHttpRequest 实例将参数提交至服务端[5].

(3)Flash 上传

在传统表单的上传功能基础上,Flash 上传方式在不刷新网页的条件下,支持多个文件批量上传以及显示上传进度等功能. 它采用Flash 作为中间代理层与服务端进行通信,以此为基础的SWFUpload、Plupload及Uploadify 等文件上传插件被广泛应用[6].

(4)第三方组件上传/插件上传

插件技术主要包括ActiveX、Applet 等,虽然可能受限于浏览器的安全性设置,但在学校及企业内部网站环境中有一定的使用价值[7]. 例如ActiveX 组件,在VB 6.0 运行环境下,使用关键的Winsock 控件来建立与服务端之间的通信,通过Socket 连接发送文件数据.文献[8]对FileUpload,SWFUpload 及SlickUpload 三种组件的特性进行了分析和评估. FileUpload 控件使用简单,但默认对上传组件的大小有限制,因而需要通过修改配置文件中响应时间和大小的限制实现大文件的上传. SWFUpload 作为一个开源的JavaScript 和Flash库,它结合了二者的功能,可以实现交互性更好的界面展示. Slickupload 是来自国外的商业组件,其在局域网的文件上传中具有良好的表现[8].

2 关键技术

2.1 Node.js

Node.js 基于事件驱动的非阻塞I/O 模型,旨在支持能够管理大量并发请求的轻量级服务器的简单而快速的开发[9]. 受益于V8,Node.js 性能优越,运行速度快,可以在服务端运行,匿名函数和闭包的使用使其在语言层面具备了异步、事件编程的特性[10]. 在处理二进制数据流时,常用的有stream 合并与buffer 合并两种方式. Node.js 中使用buffer 库实现原始数据的存储方法,数据被保存在buffer 的实例中. Node.js 中的stream流是处理流式数据的抽象接口,在处理较大数据量的文件时,采用stream 合并比buffer 合并更有优势. Buffer需要一次性将数据全部放入内存,如果数据流较大容易导致速度慢,内存爆满. 流模式合并数据则是一边读取数据一边进行操作,在空间上只占用当前处理数据区域的内存大小,有效地降低了内存的开销[11]. 同时,对于传输过程中的加密及压缩处理,stream 流具有更高的扩展性. 因此,本文选择流合并,使用Node.js 的可读流与可写流,实现读取和写入同步,提高合并效率.

2.2 HTML5 file system

在HTML5 中提供了一种通过File API 规范与本地文件进行交互的标准方法,它的主要作用是将本地文件以文件对象的形式提供给 Web 应用程序进行访问,为浏览器端应用程序的开发提供了无限可能[12].File API 提供了前端处理本地文件的能力,让图片预览、分块上传、拖拽上传等操作变为可能. 以下是本文所用到的对象简介.

(1)FileList 是一个由File 对象组成的类数组对象.

(2)File 是FileList 中的一个对象,包含文件名称(name)、大小(size)、类别(type)、修改时间(lastModified-Date)等基本信息.

(3)FileReader 用来读取文件的API,将文件读取到内存中,提供将文件读取为文本、base64 图片编码、Buffer 数据类型、二进制字符串等方法,可以实现预览图片、计算MD5 等等操作.

(4)Blob 是一个二进制数据,File 对象就继承自Blob 对象. 通过slice 方法,可以使二进制数据按照字节分块,返回的对象中包含了源 Blob 对象中指定范围内的数据[13].

2.3 Spark-md5

对分片文件的标识也是整个文件处理过程中必不可少的一部分. 异步提交的数据中必须包含文件的唯一标识来确认文件分片的顺序,验证是否上传完毕[14].MD5 生成的hash 码不可逆,可以作为文件上传的有效标识,这也是实现文件秒传的基础. Spark-md5 是基于Javascript 的前端类库,它基于文件的内容生成相应hash 值,利用File API 对文件进行分块之后再进行MD5计算,与传统的MD5 计算相比,它的传输效率很高,不容易引起浏览器卡顿、崩溃等问题.

2.4 Web worker

Node.js 和JavaScript 都是单线程编程模型,HTML5的新特性Web worker 为浏览器实现多线程操作提供了支持. 在文件上传过程中,多线程操作显然比单线程更具有优势,且不容易造成阻塞. Web worker 允许在Web 程序中并发执行多个JavaScript 脚本,每个脚本执行过程都作为一个线程,各个线程之间彼此独立,由JavaScript 引擎负责管理[15]. 线程一旦被创建,可以在主线程调用worker 线程,通过将消息发布到代码指定的事件处理程序.

3 设计与实现

3.1 整体设计

基于对大文件上传常用方法与关键技术的研究,本文设计并实现了完整的前后端大文件上传系统. 该系统基于HTTP 协议,利用HTML5 的File API 对需要上传的目标大文件进行分片处理. 同时,充分发挥CPU多核的性能,创建Web worker 线程计算和处理分片的文件,避免主线程阻塞. 通过对分片文件的MD5 校验及标记,增加文件传输的安全性. 在此基础上,通过自适应分片结合多并发上传进行优化,提高了传输速率.在服务端,服务器接收前端传输的分片文件,按分片顺序依次存储,当收到前端的合并请求,服务端使用流模式将收到的所有文件切片进行合并. 此外,在上传过的切片列表中进行查询比对,对已经上传过的相同文件无需再传,避免重复上传. 整个系统的流程示意图如图1 所示.

图1 系统流程图

3.2 前端实现

3.2.1 Hash 计算

为了使服务端对已上传的内容进行识别,必须要生成文件和切片的 hash 作为校验. 这里使用Web worker 为JavaScript 创造多线程环境,调用Worker()构造函数,新建一个名为hash 的worker 线程. 在主线程调用worker 线程,通过postMessage()函数传入文件内容切片后得到的数组fileChunkList,worker 线程利用 FileReader 读取每个切片的 ArrayBuffer 并不断传入Spark-md5 中,每计算完一个切片通过 postMessage 向主线程发送一个进度事件. 主线程通过onMessage函数监听子线程消息,待全部文件读取完成后,子线程将最终的 hash 发送给主线程. 整个流程如图2 所示.

图2 Web worker 示意图

3.2.2 自适应分片

在实际的应用场景中,所需要上传的文件大小往往是不固定的,而分块大小对文件传输有较大影响[16].因此,目前常用的设置固定大小的分片方法不具有灵活性. 自适应分片算法的核心在于,根据上传文件时的网络状况,实现切片大小的动态调整. 在当前切片文件上传完成时,通过获取当前切片文件所用上传时间来调整下一个切片文件的大小,目的是为了每次上传时切片大小与当前网速相匹配,具有更好的传输效率[17].参考TCP 协议的慢启动策略思想,从分片的小容量文件传输开始试探网络状况,根据实际测得结果动态调整下一次分片的大小[18]. 比如,如果理想的状态下每20 s 上传一个文件块,其初始文件大小为1 MB,实际计算的上传时间仅为10 s,那么可以动态的调整下一个分片的大小为2 MB. 另一种可能是实际上传所用时间为40 s,那么说明当前网络状况不足以传输1 MB 文件,下一个文件的分片大小可以改为初始值的一半. 因而,在自适应分片算法的计算方法中,设置一个初始切片文件大小为fileChunk,设置理想的上传单个分片所需时间为ts,实际上传过程中每个切片所用时间为t,那么当前切片的上传速率rate可以表示为t/ts. 此时下一切片的文件大小newFileChunk的计算方式为:

本文参照文献[4] 的参数,设置初始文件大小设为1 MB,理想的参照上传时间ts为2 s,实际上传中所用时间t通过new Date().getTime()获取上传请求前后的时间戳,得到当前切片上传时间. 利用式(1)不断计算得到新的下一切片大小,达到切片大小动态调整的效果.

切片调整部分关键代码摘录如下:

3.2.3 多并发上传

为充分利用网络带宽,采用多并发的方式进行文件上传. 并发上传的并发数受浏览器支持的最大并发数限制,超过这个值,执行过程中的并发请求需要等待.文献[7]中采用固定分片大小结合多并发上传,研究得到在双核处理器条件下,并发数为3 时上传文件的耗时出现拐点,也即上传时间出现明显的减少. 本文设置max为最大并发数,通过while 循环执行并发请求,设置counter计数,当max>0 并且当前计数值小于请求长度时进入循环体. 进入执行循环max值减少1,每次传输完成,释放并发通道,以此保证并发数在设定值. 通过对max取值3 到6 进行分别测试,得到上传耗时在max取值为5 时出现明显减少. 以此为基础结合自适应分片,在代码实现中设置并发数为5,使得文件的分片大小每5 片为一组进行自适应大小的变化,实际耗时t通过5 个切片文件的上传总耗时求平均得到. 通过这样的改进方法,得到更短的上传耗时.

多并发上传结合自适应分片算法的流程示意图如图3 所示.

图3 流程示意图

3.3 服务端实现

3.3.1 接收切片文件

对前端传递的FormData,服务端使用multiparty包进行处理,创建target 文件夹作为文件上传的存储目录. 前端在发送每个切片时都携带了唯一标识hash,服务端将处理后的分片对象从临时路径移动到切片文件夹中.

3.3.2 合并切片

服务端接收到来自前端的合并请求后,对切片所在文件夹下的所有切片进行合并. 首先采用sort()方法根据切片的下标进行排序,避免从目录读取的文件顺序发生错乱[19]. 使用 fs.createWriteStream 生成可写流,通过fs.createReadStream 生成可读流,将切片文件夹内的切片传输到目标文件夹中并合并. createWriteStream方法的两个参数控制可读流传输到可写流指定的位置.这样做能保证在并发合并多个可读流时,不必按照流的顺序一个接一个传输也能使切片传输到正确的位置[20]. 与确定上一个写入完成再读取下一个流的方式相比,多并发上传大大提高了传输效率.

3.3.3 文件秒传

文件hash 值与文件后缀作为目录,使用fse.exists-Sync 检测文件目录是否存在,如果存在,则将标志位置为false,不需要再次上传. 如果不存在,则将标志位置为true. 在此基础上,文件秒传的实现只需要在用户选择上传已存在的相同资源时,直接提示上传成功. 在前文服务端验证hash 的基础上,如果发现hash 相同的文件,说明该文件资源已经上传,可以直接返回上传成功.

4 实验分析

本文使用文献[7]中的设计方法作为对照,将固定分片上传与自适应分片上传的方法进行对比. 选取了3 个100 MB 以上不同大小的文件进行测试,文献[7]所用方法测得的时间记做原始方法用时,本文提出的方法记做改进方法. 原始方法采用固定分片大小2 MB,同时选择并发数为5 进行多并发上传; 改进方法选择相同的并发数,采用改进的自适应分片算法,以2 MB大小为起始分片大小进行上传. 浏览器选择Chrome,通过控制台的网络network 面板查看分片的请求状态以及实验结果. 在同样的网络环境下,每个文件采用两种上传方式分别进行3 次测试,统计其平均值作为对照,测试结果如表1 所示.

在文件上传的测试过程中,针对同一文件,再次上传时,经过对MD5 码的校验,可以直接实现秒传,系统弹窗提示上传成功. 表1 中传输时间的计算是从开始上传到服务端接口合并完成文件的整个过程. 由表1可以看出,本系统可以支持500 MB 以上大文件的上传,不同大小的文件上传所用时间改进方法均少于原始方法耗时,并且随着源文件大小的增大更大时比固定分片上传具有更明显的上传时间优势. 通过后端流合并的方式对分片文件进行合并,得到的上传文件与源文件一致,MD5 值的唯一标识也保证了文件秒传的实现,避免对同一文件的重复上传,节约了时间成本.

表1 实验结果(s)

5 结论

本文研究并介绍了常用的大文件上传方法以及存在的问题,对本系统所用到的关键技术Node.js 及File API 进行了阐述,通过对前后端上传过程的具体研究,实现了基于Node.js 的大文件上传系统,通过对多并发上传与自适应切片相结合的算法,实现了更具有灵活性和更高传输效率的大文件上传. 同时针对大文件的MD5 标识计算,利用Web worker 多线程计算的方式有效地避免主线程的卡顿. 该系统灵活度高,适用性强,能够在文件上传过程中提高上传效率,提升用户体验.

猜你喜欢

服务端表单线程
5G终端模拟系统随机接入过程的设计与实现
实时操作系统RT⁃Thread启动流程剖析
实时操作系统mbedOS 互斥量调度机制剖析
VFP教学的探讨与实践
浅谈网页制作中表单的教学
多人联机对战游戏的设计与实现
基于三层结构下机房管理系统的实现分析
基于三层结构下机房管理系统的实现分析
使用智能表单提高工作效率
表单化管理国内对比研究