android上传下载系列:如何优化上传的性能

转载自以下文章:

http://www.zuidaima.com/blog/2819949848316928.htm

如何优化文件上传性能?

对于文件上传性能优化,可以从两个方面来着手,即上传前的优化和上传过程中的优化。

一.上传前的优化

主要有两个思路。

思路一:通过减少文件体积,减少上传流量来优化。
思路二:通过合并小文件,减少请求数来优化。
基于这两个思路,我们尝试了以下几种方案。

1.图片压缩

如果上传的是图片类文件,存在一部分用户喜欢直接选择相机或者手机拍摄的原始文件进行上传,对于这类图片,图片的分辨极高,高达5000多。这就注定了此图片的文件体积不会太小。其实如果只是在电脑上查看,1千多的分辨率就已经足够。于是我们尝试通过js将此类图片进行缩小,得出的结果是:1张(5184×3456)大小为5M的jpg图片,缩小到(1600x1600)后,体积变成了407kb, 直接节省了4.5M的流量。

2.ZIP 合并小文件

类似于拷贝文件夹到U盘,如果小文件比较多,拷贝过程非常慢,通常我们的做法是将文件夹打包成一个文件再拷贝,速度往往要快出许多。其实文件上传也一样,如果能把体积比较小的文件合并成一个文件,请求数就会少了很多,这样是不是就提高了整体文件上传速度呢?

但是,通过测试得出的结果是不尽如意的。ZIP的运算效率太低,以至于只有在2G网络下才有速度提升,3G和wifi网络下反而变慢了。更多细节请移步到这里。

3.SPRITE 合并小图片

类似于css sprite, 将多个小图片画到一张大图片上,通过这种方式来进行合并,思路和zip合并差不多,但是采用的技术不太一样,此方案是直接用canvas来画,经过测试发现速度比zip快出来10倍,总体能带来20%左右的速度提升。

但是此方案只满足于图片类文件,且服务端需要通过位置信息还原出小文件,带来一定的服务端开发成本,目前并没有采用此方案。更多细节请移步到这里。

4.直接合并文件内容

其实,并不需要采用ZIP或者SPRIT方式合并文件,把文件读取出 arraybuffer 后是直接可以连接在一起的,之后还可以再次转成 blob 发送到服务端,或者直接发送 arraybuffer,理论上性能应该比SPRITE方案靠谱。

但是这块没有进行详细的调研,在此就不多说了。

二.上传过程中优化

主要采用并发与分块,以下将细说这两个方案。

1.并发上传

采用此方案主要是源于单一请求无法让网络达到饱和,于是我们来尝试采用并发方式看能否提高总体文件上传速度。

以下是通过测试20个1M的文件在不同的并发数下得到的总体上传时间对比图。

很明显,并发数越多速度越快!

但是,并发数越多,给服务端带来的压力也越大,如何去选择一个合适的并发数呢?
主要可以从三方面去考虑。

并发数越多,服务端压力越大,所以选择并发数不能太大!
同时每个浏览器都有固定的最大并发数限制,所以选择并发数不能超出这个值。
从上面的图标可以看出,并发数到了3以后,收益开始渐渐不明显。
如是,最佳的并发数应该是3。

2.分块上传

为什么要采用分块上传?
试想一下,如果上传的文件是一个大文件,本来上传时间就相对较久,再加上网络不稳定各种因素影响,容易导致传输中断,用户除了重传整个文件外没有更好的选择。采用分片上传可以很好地解决这个问题。

什么是分片上传?
分块上传,就是把一个大的文件分成若干块,一块一块的传输。如上面的case, 如果传输中断,仅需重传出错的分片,而不需要重传整个文件,大大减少了重传开销。

那么,采用分块上传还有哪些优势?

1.更强容错能力

如以上的case, 出错重传,仅需重传一小块。

2.可以模拟暂停与继续

对于一个http请求,其实并没有暂停功能,要不就是已完成,要不就是已中断。如果不分块,是没法做暂停功能。但是如果采用分块上传,在某个分块上传完了后不自动开始下个分块上传,是不是就实现了暂停功能?

利用此功能,就可以通过网络检测,在网络断开的时候自动暂停传输,网络恢复后,继续传输,给用户带来更好的体验效果。

3.利用并发提速

如果单独的上传一个大文件,只有采用了分块上传才能利用并发请求来提速。

4.更精准的速度跟踪

关于客户端监控上传进度,其实监控的都是客户端的发送速度,服务端有没有接收,有没有存储,是不知道的,只有在整个请求完毕,收到服务端反馈后才能确定数据已经成功接收。这样也就解释了进度显示的时候,长长出现,进度条瞬间到100%,要过一段时间才全部完成。如果采用分块上传,每个分块上传完成,可以确定这个分块服务端已经成功接收。

当然,分块也会有一定的副作用,本来是一个请求,分块后变成了多个请求,自然会带来网络开销。那么具体是个什么的情况呢?
以下是通过测试3个30M的文件在3个并发下调整不同的分片大小得出的总体时间消耗表。

可以看出来,分块越小,时间消耗越大,尤其是分块大小小到256K的时候,时间花费增长特别明显。

那么,如何选择一个合适的分块大小?

主要有三个方面的考虑。

分块越小,请求越多,开销越大。所以不能设置得太小。
分块越大,灵活度越小,前面所说的那些优势就会相对越不明显。故不能太小。
服务端一般都会有个固定大小的接受buffer(clientbodybuffer_size), 分块的大小最好是这个值的整数倍。
综合这些考虑,推荐的分块大小是2M-5M,具体size根据产品中文件上传的大小分布来定。如果上传的文件大部分是500M以上,很大的文件,建议是5M, 如果相对较小,推荐2M。

3.断点续传

有了分块上传,其实我们可以实现更多的功能。试想,如果服务端能够把每个已成功上传的分块都记住,客户端可以快速验证,条件选择跳过某个分块,是不是就实现了断点续传?

那么,断点续传能带来哪些好处?

节省流量,避免上传重复的分块。
减少用户等待时间。
可恢复的上传。出现中断,就算浏览器刷新或者是换了台电脑也能恢复到上次中断的位置。
那么现在最关键的问题是如何标识一个分块了。怎样标识让服务端好入库,同时客户端可以快速的计算出来与服务端验证,换句话说就是,如何去找出分块的唯一ID。

之前尝试过文件名+分块编号,或者再加文件大小,文件最后修改时间什么的,都无法保证分块的唯一性。于是还是直接采用 MD5 的方式来序列化文件内容吧,这样就算是文件不同名,只要内容是一致的也会正确地识别出是同一个分块。

那么现在的逻辑就是,每次分块上传前,根据内容 MD5 序列化,得到一个唯一ID,与服务端验证,如果此分块已经存在于服务端,则直接跳过此分块上传,否则上传此分块,成功后,服务端记下此分块ID。

如是,分块上传是不是具有了秒传的功能?既然分块能秒传,那么整个文件是否也可以秒传?

4.秒传

分块能秒传,整个文件当然也能秒传,关键还是看 MD5 的性能。

通过以上测试结果可以看出,如果文件大小在 10M 以内,是可以真正的秒级内完成的,大于这个值时间花费就大于1秒了,比如一个200M的文件 MD5 时间花费需要13秒左右。

但是,即便如此,相比于文件传输时间花费,MD5 的时间花费根本就不算什么。对于类似于百度云,文库这类的产品,在上传一个文件的时候很可能服务端已经存在了此文件,如果多等个几秒钟,能跳过整个文件上传,我觉得是非常划算的。

5.算法优化

另外,如果是一次上传多个文件是可以在算法上去优化这个过程的。

1.验证过程提前到当前文件的传输期

如果当前文件已经在传输了,这个时候,用户是处于等待状态,机器也处于等待期,如果把下一个文件的验证过程移至此过程,那么用户的等待 MD5 的时间和等待当前文件传输完成的时间就重合了。这样用户就只需要等待第一个文件的验证过程。

2.小文件优先处理,减少用户等待时间

因为第一个文件的验证等待无法避免,如果第一个文件处理的文件越小,是不是等待的时间就越短?所以把队列中最小的一个文件放到第一个优先处理可以进一步减少用户等待时间

3.更换序列化算法,取段MD5

其实对于某些二进制文件,如JPEG,前面一段数据记录了很多此图片的信息,比如:拍摄时间,相机名称,图片尺寸,图片旋转度等等,直接 MD5 这一段数据基本上就可以保证此文件的唯一性了。只要取段的总大小小于10M,再大的文件也能在1秒内完成序列号工作。

你可能感兴趣的:(android提高)