标签:
笔者在项目中处理大文件上传的需求,仿照七牛云存储的接口设计。然而,在服务器端文件合并时遇到了很大的问题:合并太慢。本文记录了当时的思路和解决的方案
文件上传是个很常见的需求。尽管HTTP是基于TCP上层的协议,但是HTTP协议本身并不适合处理超大的请求体,文件上传有很大的稳定性问题,如果中途断开了,将前功尽弃。为了改善用户体验或者缓解服务器压力,通常会考虑将文件分成小片,将小片一个个上传,如果中途断开了也能从某个失败的小片开始继续上传。
在前端的处理上,对于Web页面,可以采用plupload作为上传组件,该组件支持html5、flash、sl等多种上传方式,因此,可以提供较好的浏览器兼容性。七牛云存储的js-sdk就是基于这个组件开发的。不过本文的重点并不是讨论前端技术,关于前端就到此为止。
既然文件被分成片上传,那么自然在服务器端需要将分片合并成原始的文件,那么这里存在两种策略
这种方式要注意:
七牛云存储就是使用的这种策略,具体的实现方式如下:
这种设计解决了客户端中途停止上传带来的服务器端资源的浪费,因为分片都是正在进行态,可以对时间很早的分片进行清理。而且分片被记录了下来,容易对分片进行一些管理。
现在进入本文的重点。文件合并是IO操作,IO操作是最耗时的工作了。上文的第一种策略,有一个好处是文件的合并是在上传的过程中完成的,对于用户来说几乎感知不到文件Append时的延时。然而,第二种策略的文件合并却是在一个时刻同时进行的。笔者测试过,即使是4-5个4MB的分片,也会使客户端有明显的延迟感。如果分片再多的话,延迟将更大,甚至请求超时,这是不能接受的。
但是笔者在七牛云上的测试,合并的请求在七牛的服务器端几乎没有延时。为此,笔者还发了一问:七牛云mkfile如何实现将大量的文件chunk快速合并的,但是七牛的技术太“吝啬”,一点也不透露。那该怎么办呢?
由于是将分片合并,那么很容易会想到并行。类似归并排序的思想,将合并任务分开,然后通过集群服务器的协调完成合并。但是笔者对这方面是知之甚少,而且这种方案会使原本简单的架构变的异常复杂,不敢采用这种方案。而且感觉会有坑:
仔细思考合并这个动作,实际上是将多个文件在文件系统里面复制了一次,而文件的内容并没有任何的变化。如果能够在文件系统层面将分片直接连接起来话,合并文件仅仅是修改一些指针,速度将十分的快。不过文件系统各不相同,能不能实现还需要看。而且,由于笔者使用nfs作为数据存储,nfs的文件读写完全是通过接口提供的,接口也不提供底层的文件系统操作,所以似乎是无法实现。
再思考下去,如果文件系统无法做到将分片直接连接起来的的话,那么从用户接口层(HTTP)是否能做到呢?试想,通过HTTP的方式提供文件的访问,如果HTTP服务器能够知道这个文件是由多个小文件按何种顺序组成的,那么就可以按照顺序将分片依次放在同一个HTTP流中返回,对用户来说一次请求还是得到一个文件,好像文件是合并好的一样,但实际上文件在文件系统并不存在。
这样做需要单独将分片的顺序维护好,每次都要读出分片的顺序和位置,然后依次一个个写入HTTP流中。但是高层的Web编程框架似乎无法支持这种做法。
笔者立刻想到了之前用过的Nginx模块mod_zip,这个模块能够将多个文件打包以zip流的方式返回。现在的需求其实跟这个模块的工作几乎差不多,甚至还要更简单。苦苦寻觅网上的Nginx模块,似乎没有找到笔者需要的,于是决定自己基于mod_zip开发一个。幸好,之前用mod_zip的时候看过一些源码。
目前这个模块笔者命名为mod_pieces,已经开发完成并在windows和linux两个平台下测试通过,唯一不好的是无法支持HTTP Range,HTTP Range有点难弄,以后可能有时间再慢慢实现。代码还没有整理,有时间放到Github上去共享。
mod_pieces的工作方式和原理和mod_zip很相似,有进一步需求的读者可以移步至:利用Nginx第三方模块,实现附件打包下载
现在用户那头可以"欺骗"成功了,但是如果系统本身需要对文件进一步处理,比如视频的格式转换,那么还是需要将文件合并起来的,不过这个时候就可以用一个后台的服务异步的慢慢做了,用户不会感知。基于这些复杂的问题,笔者已经把文件上传下载和处理作为的一个全新的产品功能独立出来,以支持主产品对文件的各种功能需求。
本文没有一张图片,没有一行代码,有些不适应。文字虽短,但是这些东西都是经过笔者的实践并且有感而发,希望有个总结,并能够带来更多的交流。
标签:
原文地址:http://my.oschina.net/zhangjie830621/blog/475200