超大文件上传就无法避免断点上传,想要实现秒传你就饶不开文件特征。本文不涉及具体代码。只讲我自己对断点上传于秒传的学习经验。
首先我们分析一下要实现断点续传于秒传的具体要点。
1、前端拆分碎片
2、前端生成文件特征
3、传输过程
4、后台接收文件合并
5、合并完成后,后台的特征对比
看似原理简单,粗狂分析一下就可以动手了,其实每一步都有很多技术难点于陷阱。
1、前端拆分文件碎片
在以前只要断点续传就必须采用前端控件。控件主要工作就是在拆分文件与碎片与分析md5。现在的html5本身已经支持对文件拆分读取上传。至于如何拆分自己搜索一下很容易找到demo。这里陷阱不多只要考虑一下兼容性就可以了,IE系列可以考虑用flash。
2、前端生成文件特征
这里说明一下,我没有直接说是md5 是因为md5现在在大型网盘项目里碰撞的几率已经很高了,不能完全依赖md5,尽量多取几种哈西值一起传到后端做特征。(取文件的几部分做特征是很不靠谱的不用考虑了)
在前端生成特征才能实现秒传功能(也许有不需要的方法反正我不知道)。文件生成哈西值,是采用流运算方式把整个文件读取分析一遍。由于需要读取,那么超大文件的耗时是不可避免的。目前js 有个很好的库类可以支持采用文件流方式计算md5 。spark-md5.js 这是个好东西。只需要把文件碎片挨个分析一遍,文件全部跑完就能生成一个md5了理论上可以生成任意大小文件的md5。测试过8G的文件生成一个md5码需要十几分钟。这取决于客户机器性能和磁盘速度。网上还有其他哈西的文件流获取算法,请自行百度。
3、传输
传输现在html5现在可以采用ajax和websocket两种方式可以任选。这里我选用的是自己熟悉的ajax方式,websocket只是初略的看了一下。就不发表具体看法了。
ajax生成传输队列的时候有个坑需要注意一下。当拆分碎片上传,也就是每一个碎片是一个ajax请求。那么一个8G的文件将会生成多少请求呢。一个循环下去浏览器死了。所以要采用递归方式进行控制ajax堵塞数量。一次循环传输10个碎片,传完在传下一组。
4、后台的文件合并。
接收到文件就需要考虑合并问题了。我之前百度了一下。基本都告诉我等待文件碎片全部上传完成然后合并。当我上传一个8G的文件到后台后然后循环合并,太恐怖了上万的碎片合并。想想就难受。
第一个想法是放到队里里在后面慢慢跑。后来放弃了感觉体验不是很好。
后来想到了边上传边合并,把合并时间分散到每一个请求里。每个请求只处理1-2mb的碎片还是很快的。
刚开始参考迅雷想生成一个和目标文件体积相等的空白文件。然后等待碎片来拼入文件。后来发现php不能快速干这个事情。有其他方法也懒着研究了。就写了一个扩容算法
算法说明:
收到请求后如果第一个碎片是2号,那么在生成的文件里填充字符串把1号碎片的位置留下,然后写入2号碎片,等1号碎片 达到,在把1号碎片写入他自己的位置。
算法基本上很快没有什么延时。好在我们不是做下载,上传时不会直接收到最后的碎片。
写好后进行测试发现文件大于2g就不在生成了。块是很快了不过不能处理大文件。后来发现是php文件指针在32位系统下最大值是2G。如果移动大于2G 就会返回-1失败。那么理论上这个算法最大生成文件是2G。(或者是4G ,指针从文件尾巴向前计算还有2g不过我没有继续研究下去,理论上php读取文件内容最大值是4G)下面换一种算法。
最简单的办法,就是往文件尾巴里追加内容,这样就不受文件指针大小影响了。ajax 在并发的时候到达顺序是不一样的。但是这个不影响,不会对我们后台编程产生太大的障碍。后台进行搬砖一样的排序写入就好了,。
举例:
如果是第一个碎片收到的是2号,那么把碎片文件拷贝出来备用。
下一个请求碎片如果是1那么写入文件,写入后询问备用里有没有2号如果有写入。以此类推。
这个算法和上面的扩容方法比起来效率要低了一些。因为要做计数器和询问利用。但是影响不是很大。
我两种算法都采用了如果文件小于1.99g用上面得扩容算法,大于就用排序。
合并也完成了剩下就是对结果进行效验了。
5、后台文件效验。
不要相信 php 或者其他语言的 md5_file这玩意处理不来大文件。几个G基本上就崩溃了。记住一点只要你想获得文件的md5 ,就必须对这个文件进行读取分析一遍才行怎么做都是很耗时的。
像百度那种大型网盘已经完成对TB级别md5随时生成,真的很牛叉。后来我分析了一下他们应该是这么做的。
应该和我们处理文件合并的方式差不多。把计算md5的时间分摊到了每一个碎片上。我们可以这么理解,md5 需要顺序分段读取整个文件进行分析才能生成一个md5值,那么我们在上传过程中每一个碎片都是文件的一部分。我们只要顺序分析每一个文件碎片,那么上传完成了md5也就生成了(理论上也能到TB级别)。
我尝试用这种方式找了一个php 版本的md5算法,改进了一下,实现了超级大文件上传完成后台即使获得到md5的类。(测试过与 md5_file函数获得的值一样.)
经过测试完全可行。 "但是,可但是,但可是" php真的不适合干这种大型纯运算很慢,基本上js 算md5的效率 是php几十倍。一个2mb的碎片计算需要9秒左右。
这么做每一个碎片后台响应时间都超长,也就直接影响上传速度了。
几经研究无果。决定放弃php 找师哥用c写个扩展来干这个事情了。
项目基本完成了,至于其他的续传 和 秒传 没什么好讲的,你都已经有碎片了续传不是问题。你都能获得前后端特征码了,那么秒传也不是问题了。
这个过程最耗时的是前端计md5,其他运算都分摊到每一碎片上了。
上面的东西没有经过大小项目验证全是理论上的,也许我是错的。我说的都是我自己理解的有错误的地方请指出,我会改进不要误导群众。