服务器端文件分片合并的思考和实践

笔者在项目中处理大文件上传的需求,仿照七牛云存储的接口设计。然而,在服务器端文件合并时遇到了很大的问题:合并太慢。本文记录了当时的思路和解决的方案

大文件的需求

文件上传是个很常见的需求。尽管HTTP是基于TCP上层的协议,但是HTTP协议本身并不适合处理超大的请求体,文件上传有很大的稳定性问题,如果中途断开了,将前功尽弃。为了改善用户体验或者缓解服务器压力,通常会考虑将文件分成小片,将小片一个个上传,如果中途断开了也能从某个失败的小片开始继续上传。

在前端的处理上,对于Web页面,可以采用plupload作为上传组件,该组件支持html5、flash、sl等多种上传方式,因此,可以提供较好的浏览器兼容性。七牛云存储的js-sdk就是基于这个组件开发的。不过本文的重点并不是讨论前端技术,关于前端就到此为止。

服务器端的策略

既然文件被分成片上传,那么自然在服务器端需要将分片合并成原始的文件,那么这里存在两种策略

  • 边传边合并:每上传一个分片,就将分片合并到文件的后面
  • 传完一起合并:先将分片保存起来,客户端发起一个合并请求时,再将分片合并成一个文件

边传边合并

这种方式要注意:

  • 由于HTTP的无状态性,这种方式需要客户端和服务器端维持一个标识。服务器端根据标识,才能知道分片应该向哪个目标文件Append
  • 如果客户端中途停止上传,那么保存在服务器上的目标文件将成为垃圾文件,文件即不能被删除又没有利用价值。因为服务端无法知道文件究竟的完成状态还是正在进行状态
  • 由于分片直接合并进了文件,无法管理分片

传完一起合并

七牛云存储就是使用的这种策略,具体的实现方式如下:

  1. 每上传一个分片,服务端将分片保存下来,并返回客户端一个唯一标识ctx,这个标识ctx与当前这个分片关联
  2. 客户端应当妥善保存每个分片的标识,以及它们的先后顺序
  3. 服务端需要提供一个接口,客户端用这个接口请求分片合并,在请求时候将标识按顺序告知服务端,ctx0,ctx1,ctx2...
  4. 服务端根据标识的顺序找到对应的分片,并合并成完成的文件

这种设计解决了客户端中途停止上传带来的服务器端资源的浪费,因为分片都是正在进行态,可以对时间很早的分片进行清理。而且分片被记录了下来,容易对分片进行一些管理。

服务器端的分片合并

现在进入本文的重点。文件合并是IO操作,IO操作是最耗时的工作了。上文的第一种策略,有一个好处是文件的合并是在上传的过程中完成的,对于用户来说几乎感知不到文件Append时的延时。然而,第二种策略的文件合并却是在一个时刻同时进行的。笔者测试过,即使是4-5个4MB的分片,也会使客户端有明显的延迟感。如果分片再多的话,延迟将更大,甚至请求超时,这是不能接受的。

但是笔者在七牛云上的测试,合并的请求在七牛的服务器端几乎没有延时。为此,笔者还发了一问:七牛云mkfile如何实现将大量的文件chunk快速合并的,但是七牛的技术太“吝啬”,一点也不透露。那该怎么办呢?

并行处理?

由于是将分片合并,那么很容易会想到并行。类似归并排序的思想,将合并任务分开,然后通过集群服务器的协调完成合并。但是笔者对这方面是知之甚少,而且这种方案会使原本简单的架构变的异常复杂,不敢采用这种方案。而且感觉会有坑:

  • 并行处理往往是异步的,如何处理好与web服务器的同步
  • 再怎么分任务,合并终究是IO操作,IO操作总是要耗费时间的

文件系统底层处理?

仔细思考合并这个动作,实际上是将多个文件在文件系统里面复制了一次,而文件的内容并没有任何的变化。如果能够在文件系统层面将分片直接连接起来话,合并文件仅仅是修改一些指针,速度将十分的快。不过文件系统各不相同,能不能实现还需要看。而且,由于笔者使用nfs作为数据存储,nfs的文件读写完全是通过接口提供的,接口也不提供底层的文件系统操作,所以似乎是无法实现。

为什么非要合并!

再思考下去,如果文件系统无法做到将分片直接连接起来的的话,那么从用户接口层(HTTP)是否能做到呢?试想,通过HTTP的方式提供文件的访问,如果HTTP服务器能够知道这个文件是由多个小文件按何种顺序组成的,那么就可以按照顺序将分片依次放在同一个HTTP流中返回,对用户来说一次请求还是得到一个文件,好像文件是合并好的一样,但实际上文件在文件系统并不存在。

这样做需要单独将分片的顺序维护好,每次都要读出分片的顺序和位置,然后依次一个个写入HTTP流中。但是高层的Web编程框架似乎无法支持这种做法。

巧用Nginx避免文件合并

笔者立刻想到了之前用过的Nginx模块mod_zip,这个模块能够将多个文件打包以zip流的方式返回。现在的需求其实跟这个模块的工作几乎差不多,甚至还要更简单。苦苦寻觅网上的Nginx模块,似乎没有找到笔者需要的,于是决定自己基于mod_zip开发一个。幸好,之前用mod_zip的时候看过一些源码。

目前这个模块笔者命名为mod_pieces,已经开发完成并在windows和linux两个平台下测试通过,唯一不好的是无法支持HTTP Range,HTTP Range有点难弄,以后可能有时间再慢慢实现。代码还没有整理,有时间放到Github上去共享。

mod_pieces的工作方式和原理和mod_zip很相似,有进一步需求的读者可以移步至:利用Nginx第三方模块,实现附件打包下载

后续还是要合并的

现在用户那头可以"欺骗"成功了,但是如果系统本身需要对文件进一步处理,比如视频的格式转换,那么还是需要将文件合并起来的,不过这个时候就可以用一个后台的服务异步的慢慢做了,用户不会感知。基于这些复杂的问题,笔者已经把文件上传下载和处理作为的一个全新的产品功能独立出来,以支持主产品对文件的各种功能需求。

后记

本文没有一张图片,没有一行代码,有些不适应。文字虽短,但是这些东西都是经过笔者的实践并且有感而发,希望有个总结,并能够带来更多的交流。

时间: 2024-10-30 06:56:20

服务器端文件分片合并的思考和实践的相关文章

plupload 大文件分片上传与PHP分片合并探索

最近老大分给我了做一个电影cms系统,其中涉及到一个功能,使用七牛云的文件上传功能.七牛javascript skd,使用起来很方便,屏蔽了许多的技术细节.如果只满足与调用sdk,那么可能工作中也就没有什么收获了.其中对七牛云的服务很佩服的一点是,无论我上传多大的文件,当我文件最后一片上传完成的时候,就立刻返回到文件链接,这个问题我想了好久,也不知道七牛是如何做到的. 七牛云的sdk分为 JavaScript 与 PHP端. JavaScript端的作用是提供文件上传功能,客户端(浏览器)无需关

服务器端json数据文件分割合并解决方案

问题引入 Json 是什么就不多说了,本文把Json理解成一种协议. 印象之中,Json貌似是前端的专属,其实不然,服务器端组织数据,依然可以用Json协议. 比如说,某公司有一套测评题目(基于Json协议),这些题目比较珍贵,不想直接放在js中,所以就将题目文件放在服务器端,然后通过一个接口去请求,多一层控制,就多了一层保护,通过在接口上加权限,可保证数据安全. 如此一来,服务器端必定会有一个Json文件(纯文本文件),Json文件中包含Json数据. 假设Json数据结构如下: 1 { 2

SVN分支/合并原理及最佳实践

SVN分支/合并原理及最佳实践 转载自:http://blog.csdn.net/e3002/article/details/21469437 使用svn几年了,一直对分支和合并敬而远之,一来是因为分支的管理不该我操心,二来即使涉及到分支的管理,也不敢贸然使用合并功能,生怕合并出了问题对团队造成不良影响,最主要的原因是,自己对分支的目的和合并的方法不甚了解,这才是硬伤. 最近由于适配机型的需要(本人从事手机客户端的开发),需要经常接触分支和合并两项工作,突然发现这玩意整不明白很难开展工作,遂这两

iOS大文件分片上传和断点续传

总结一下大文件分片上传和断点续传的问题.因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况.http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件切片(分块),但这不是我们现在说的重点,我们要做的事是保证在网络中断后1G的文件已上传的那部分在下次网络连接时不必再重传.所以我们本地在上传的时候,要将大文件进行分片,比如分成1024*1024B,即将大文件分成1M的片进行上传,服务器在接收后,再将这些片合并成原始文件,这就是分片的基本原理.断点续

网络加速手段之一,JS文件资源合并下载

有过ftp下载文件经验的人都有体验,单独下载一个100M的文件比分开下载100个1M的文件速度快很多,这是因为下载需要解析域名,建立连接等额外开销的时间,因此下载100个文件就要做100次这种额外的开销.因此如果把这个100个文件整合成一个文件下载速度会快很多. 基于上述的方法,可以对HTML中的JS文件也做类似的处理,无论js文件多少个,通过配置文件,把N个js文件合并到一个文件下载.实现手段通过httpHandler.具体代码如下: web.config文件中: <httpHandlers>

大文件分片上传,断点续传,秒传 实现

前段时间做视频上传业务,通过网页上传视频到服务器. 视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:1,文件过大,超出服务端的请求大小限制:2,请求时间过长,请求超时:3,传输中断,必须重新上传导致前功尽弃: 解决方案: 1,修改服务端上传的限制配置:Nginx 以及 PHP 的上传文件限制 不宜过大,一般5M 左右为好: 2,大文件分片,一片一片的传到服务端,再由服务端合并.这么做的好处在于一旦上传失败只是损失一个分片而已,不用整个文件重传,而且每个分

腾讯IVWEB前端工程化工具feflow思考与实践

本篇文章主要介绍腾讯IVWEB团队从0到1在工程化的思考和实践.feflow的全称是Front-end flow(前端工作流),致力于提升研发效率和规范的工程化解决方案.愿景是通过feflow,可以使项目创建.开发.构建.规范检查到最终项目上线的整个过程更加自动化和标准化. 要解决的问题 项目的目录结构按约定生成 团队有一套开发规范进行约束 支持多种类型的构建,包括Fis构建和webpack构建 团队内部的代码贡献统计.离线包内置App等 为了解决上述问题,我们于17年2月底开始投入工程化fef

大文件分片上传

Vue项目中遇到了大文件分片上传的问题,之前用过webuploader,索性就把Vue2.0与webuploader结合起来使用,封装了一个vue的上传组件,使用起来也比较舒爽. 上传就上传吧,为什么搞得那么麻烦,用分片上传? 分片与并发结合,将一个大文件分割成多块,并发上传,极大地提高大文件的上传速度.当网络问题导致传输错误时,只需要重传出错分片,而不是整个文件.另外分片传输能够更加实时的跟踪上传进度. 实现后的界面: 主要是两个文件,封装的上传组件和具体的ui页面,上传组件代码下面有列出来.

前后端分离的思考与实践(六)

原文出处: 淘宝UED - 筱谷 Nginx + Node.js + Java 的软件栈部署实践 起 关于前后端分享的思考,我们已经有五篇文章阐述思路与设计.本文介绍淘宝网收藏夹将 Node.js 引入传统技术栈的具体实践. 淘宝网线上应用的传统软件栈结构为 Nginx + Velocity + Java,即: 在这个体系中,Nginx 将请求转发给 Java 应用,后者处理完事务,再将数据用 Velocity 模板渲染成最终的页面. 引入 Node.js 之后,我们势必要面临以下几个问题: 技