佛系技术方案——HTML5和zip在文件下载中的应用

低调大叔

在写这篇文章之前,先缅怀一下计算机领域的伟大先驱菲利普·卡兹(Phil Katz)先生。照片中的他,拥有着中年程序员标配的发福身材和严重后移的发迹线,眼神中透着迷离和睿智,胡子拉碴的形象,笑出了码农们惯有的憨厚本分以及和蔼可亲。30年前,这位大牛刚刚25岁,在这个大部分码农才离开象牙塔、还在勒紧裤腰带攒首付的时候,他研发的软件已经在全球风靡。本有机会成为下一个比尔盖茨的他,却是共享软件的忠实信徒,他最终开源了他的发明,也就是现在最通用的压缩算法之一“zip”。

就在不久前的2017年11月3日,这位精通数学和计算机的天才本该度过属于他的55岁生日。然而,上帝却选择在千禧年(2000年)一个春天的深夜将他带向了天堂。临死前的他酗酒无度、脾性古怪,最终导致众叛亲离、穷困潦倒,陪伴自己走完生命最后一秒的只剩下那个空酒瓶。

菲利普·卡兹曾和广大码农一样热爱编程到深夜,他把一个个孤独的夜晚转化成了对技术的革新。IT发展史上向来不缺为科技进步添砖加瓦之人,每一名从业者都在默默贡献,所以IT业的现状甚至已经远远超出了前人科幻电影中的想象。或许后人记不住每一个人的名字,但是科技革命的成果中满是贡献者们流逝的芳华。

一个古老的小问题

闲话扯得比较远,这次分享的主题是前端zip压缩在系统开发中的应用。

问题源自业务方希望将一些后台数据导出为excel的需求。阅文目前是行业龙头,每天都有海量数据产生和更新,事关公司的发展壮大,各个业务方自然少不了对各类数据的关心。Excel表格功能强大、跨系统支持度好、利于备份与共享,所以成为数据也和运营等业务方们青睐的数据汇总、分析工具,笔者所在的内容平台部门于是就经常会有一些开发各类导出excel的任务。

阅文集团使用的技术架构与很多主流的互联网公司类似,前端页面多数是通过PHP对外输出的。PHP导出excel并不是一件新鲜事,因为几年以前,外国友人已经开发出PHPExcel这个用于操作excel的php类库。虽然该库类已经有三年没怎么更新,在兼容性、易用性和功能完备性上尚有待提高,好在绝大多数情况下,使用者不要求过于花哨的excel输出,PHPExcel处理起来还算是游刃有余。

不过,问题的痛点在于php本身的局限性。php虽然是业界津津乐道的世界上最好的语言,但是这门语言设计的初衷是做页面输出,并非用于复杂运算。导出excel本身往往带有大量数据查询、格式化处理,时间复杂度和空间复杂度上都高出一般页面很多数量级。Nginx本身拥有最大执行时间的限制,所以在导出excel的时候很容易出现504 Gateway Timeout的错误。即便没有超时,excel的最终输出时间对于用户是不可知的,页面长期处于假死状态,也不符合良好用户体验的设计原则。

为了解决这个问题,业内比较通用的解决方案是采用异步输出。所谓异步,指的是请求导出excel和用户最终下载excel的操作是异步的。前端的php页面只负责将用户请求输出的信息记录到数据库里,最终的数据的整合输出由java、python等偏后端的语言负责完成,导出的excel结果另外存一份到服务器上,供用户择日下载。

这种模式有不少好处。一来,可以解决单纯靠phpexcel输出导致的超时和前端服务器资源占用过大的问题。二来,打开一个几万行的excel需要很长时间并且消耗很多内存,用户的一举一动都有可能死机,所以用户面对这么一个大文件时内心是崩溃的;异步方式可以把大文件分片,而后把分片的文件打包成一个压缩文件,解决了单文件打开不便的问题。

下载技术的新航道

此次介绍的是另外一个方案,在这里暂且称之为半异步。所谓半异步,也就是前端页面异步,后端输出同步。这里要借助一个js库类是zip.js。这一个库类主要依赖了HTML5的File API(主要为FileReader和Blob对象)和TypedArray,因此对浏览器有一定要求。下图中为这两项支持度,绿色为支持,红色为不支持。可以看到,除了令前端工程师的深恶痛绝的IE在9及以下版本中不支持外,其他浏览器都跑得转。

Zip.js提供了一个叫做HttpReader的类,这个类可以通过一个url拉取对应的内容。这样的话,我们可以提供给zip.js一个下载列表,然后把资源化整为零,一点点拉取下来。如此,也就不会出现长时间处理下载数据导致服务器超时的问题了。如下图所示,每个文件的下载是分为两步的。第一步发送一个HEAD请求,从Content-Length中获取文件的大小,如果不包含这个信息,则会用arraybuffer的格式接收数据并计算文件byte大小,第二步再发送一个GET请求获得实际的数据,两步请求的都是同一个地址。

文件下载到本地,再下一步就是压缩了。压缩是Zip.js实现的最核心功能,使用了开源的deflate压缩算法,是基于Jzlib的源码开发的。Deflate是同时使用了LZ77算法与哈夫曼编码的一个无损数据压缩算法,发明人是前文提到的菲利普·卡兹,这是他留给世界的伟大遗产。相比另一个流传广泛的rar压缩算法,deflate的算法虽然压缩率上稍占劣势,但是速度上更胜一筹。更重要的是deflate算法开放源码,而rar算法私有,因此不在考虑之列。对于一些新兴的压缩算法,诸如谷歌系的snappy以及LZO、LZF、FastLZ、 QuickLZ等等,很多也是开源的,而且在总结前人经验的基础上,压缩速度和压缩率上都有不少的提高,但是,这些算法目前尚未被开发成前端压缩库类。在我们的应用场景中,zip.js可能更多做的是打包,而不是高质量无损压缩,且zip格式的支持度更加广泛。根据奥卡姆剃刀原理,如无必要,勿增实体,deflate算法作为一个久经考验的压缩届战士,被使用在这里更适合不过了。

众所周知,javascript是一种单进程单线程的语言,而压缩是一个耗时很高的操作,如果将压缩在程序中同步执行,页面势必会长时间处于假死状态。为了解决这个问题,我们要用到web workers。Web workers工作机制和javascript的ajax处理类似,都是把任务托管给另外一个独立线程,线程之间通过postMessage方法和onmessage事件处理函数交互,web workers线程完成任务后会通过postMessage方法传递消息给主javascript线程,主线程通过onmessage方法接受事件并进行后续处理。其时序图模型大致如下:

Zip.js使用web workers实现了异步压缩文件。各大主流浏览器对web workers的支持度和File API 差不多(注:Web workers分为专用线程型和共享线程型两种,此处我们用到的是专用线程型)。对于IE9浏览器,zip.js也可以使用,但是需要选择非web workers的模式。

运用zip.js可以把压缩处理做在浏览器端,这样会有很多好处。一来可以降低了服务器的压力,不再需要在服务器进行复杂的处理;二来节省了服务器的空间,因为我们不再需要存储和管理因为用户下载产生的打包文件;三来降低了开发的难度,不再需要开发者额外掌握一门用于批处理的语言;四来,下载的进度对使用者是可见的,这相比批处理完成的下载而言更加用户友好。对于诸如音频、视频、图片、以及周期性产生的数据(如excel日报表、周报表等),很适合用这种方式下载。

说到这里,不得不再梳理一下服务器上压缩文件下载和浏览器端压缩下载的不同点,如下面表格所示,浏览器端压缩的优势还是不言而喻的。

由于使用到了HTML5的file api和web workers等技术,这中下载方式直接使用到对外部用户的业务时一般要考虑作低版本浏览器兼容。不过,在为善解人意的内部业务同事开发系统时,我们可以直接建议他们去使用chrome、firefox国际通用浏览器或者不开兼容模式的QQ、搜狗、百度等国内高端浏览器。这样,我们不但避免了事倍功半的业务开发工作,而且也算是为HTML5等前端技术的用户推广尽了绵薄之力。

双刃剑的另一面

传销了一波,似乎这项新技术已经成为了未来的趋势。存在即是合理,任何一种技术都有它应用的场景和使用价值。本文目的不在分出技术优劣,也不求引战。正如一句耳熟能详的谚语所说的——“Every coin has two sides”,前端的压缩更可以说是一种佛系的解决方案,它的优势足够明显,但也有相当的局限性。

现实场景中,用户的电脑很可能是在满载状态的,大量吃内存和CPU的软件同时运转,浏览器也开着N多未关闭的页面。数据压缩是一个CPU密集型操作,有小概率会造成电脑及浏览器卡顿。笔者未实测,但是某些情况下,不排除压缩造成浏览器长期假死或者彻底崩溃的可能。所以开发中,能不能使用前端压缩,需要根据实际情况做一个考量。

再者,由于压缩的主要工作在前端,所以耗时会比服务器压缩时间更长,并且在此过程中浏览器和服务器要保持通信状态。当下载、压缩的时间特别长的时候,浏览器和电脑都不可以被关闭。而且,意外的浏览器和电脑关闭都会让之前的下载与压缩前功尽弃。这些都会给用户带来一定的不便。

另外一个需要考虑的问题是带宽损耗。对于音频、图片、excel等格式复杂的文件,zip的压缩率一般在10%~40%之间,客户端压缩将比服务器压缩多损耗少量的带宽和流量。对于txt等纯文本格式,zip的压缩率可能会达到90%,这个时候在服务器压缩就会节省相当大量的带宽资源了。所以,在带宽和流量更加宝贵的时候,还是推荐使用服务器压缩的办法。

结语

后台系统对内服务、用户量少、业务复杂多变,很可能做不到对公众站点那么美观易用,偶尔也会遇到业务方的同事吐槽系统设定僵化,需要使用者去适应系统,而不是系统提供给使用者便捷的服务。很多时候,开发是在业务方、产品、设计、开发的相互妥协之中做出了一个令所有人都没法完全满意的结果。然而,人毕竟是使用的主体,我们努力工作、发展科技,为的是提供给自己更便捷舒适的生活,而不是反被之束缚。以人为本是我们的目标,不论对内部用户还是对外部用户,也不论从产品还是从公司层面。很庆幸的是,现在是一个技术飞速发展的时代,技术的革新推动着用户习惯的改变,用户的需求也推动着技术的不断进步,各种炫酷的黑科技层出不穷。通过应用这些黑科技,我们可以做些微小的工作,带给用户更优质的体验,让他们真正把系统当成助手,带着更加愉悦的心情去工作。在平凡的工作努力做些不平凡的事,也算是佛系出世入世的开发思路了。

以上是本人对zip压缩技术在前端开发中使用的拙见,如有谬误敬请指正!同时,欢迎感兴趣的朋友和我讨论!

本文作者

笔名:貔貅

目前就职于阅文集团内容中心,主要负责对内业务系统的研发工作。在多年的全栈开发中对C、PHP、Python、Javascript等语言都有较多实践,崇尚多元化的技术解决方案,开源软件信仰粉。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180808G191NZ00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券