美文网首页
打包下载服务

打包下载服务

作者: 拙嘴笨腮 | 来源:发表于2017-10-16 16:00 被阅读0次

    有组织就有协作,有协作就有共享,想把共享作为资源保留就要屯盘建库。你产品的用户接下来会问:“我能否把选好的资源打包下载呢?”,衣食父母的吩咐这得接着。如何设计一个靠谱的打包下载服务呢?

    摘要如下
    
    要求
    - 即刻下载
    - 浏览器支持
    - 独立可复用
    - 并发访问
    - 资源占用小
    - 安全
    - 高可用、高并发
    
    设计
    - 性能考虑:放弃压缩
    - 资源考虑:按流处理
    - 并发考虑:Golang实现web服务
    - 安全考虑:secure-token auth
    - 独立可复用考虑:独立无状态服务
    - 浏览器支持考虑:小心处理
    - 高可用、高并发考虑:集群方案
    
    实现
    

    要求

    注定是产品经理和技术总监都必须满意的,架构师心里暗自打鼓。

    即刻下载

    产品经理说了:点击打包下载按钮之后还提示让用户等 “ZIP文件准备就绪,请点击链接” 的通知再开始下载,这是不能接受的,说好的无需等待呢?

    技术总监说了:点击打包下载按钮之后就 Loading 十几秒甚至几十秒直到下载开始,这是不能接受的。性能要好,否则大文件的话到底等到什么时候去?

    浏览器支持

    产品经理说了:产品的用户群挺广,用什么浏览器的用户都有,都能支持的吧?特别是从IE下载,文件名可不能是乱码的。

    独立可复用

    技术总监说了:做一个服务,就要能独立部署,要能被几个产品复用。每个产品都来一套的话,可接受不了。

    产品经理说了:每个产品要下载的资源可能来自不同地方,要能支持任意源地址

    并发访问

    产品经理说了:我们估计了初期同时使用打包下载的用户数,支持千人并发访问就可以了,图片文档下载嘛文件都不大,我们会限制让用户一次不能下载很多文件的。

    资源占用小

    技术总监说了:经费有限,初期用户用得也不频繁,这个服务只能给你们划一台低配置的VM,2核CPU、2G内存、20GB硬盘够了吧,日志不能丢哦,好好干......

    安全

    产品经理说了:网上很多资源都存在被盗链或窃取的情况,用户A打包下载zip文件之后,这个zip文件不会被其他人再下载或盗链吧?要保证安全哦。

    高可用、高并发

    技术总监说了:用户会积少成多,架构设计要走在前面。高可用性如何保证?更高的并发访问如何应对?

    不断点头称是的架构师此刻脑子飞快的运转着,一面在心里再次确认这些要求不是空想,一面如玩魔方一般拼凑思路进行设计。

    设计

    传统的文件打压缩包方案

    步骤如下,缺点在括号内。这些资源消耗在并发量增大时会急剧上升,VM资源成为瓶颈。

    1. 从各源地址下载源文件到服务器(有等待时间,源文件临时占硬盘)
    2. 打包压缩(有等待时间,压缩很耗CPU)
    3. 提供压缩包文件可对外下载(压缩包占硬盘)

    性能考虑:放弃压缩

    在前文列出的《要求》里,并没有着重提到打包的压缩比,其原因

    • 用户最需要的是打包,即免去一个一个下载文件的繁琐。
    • 用户关心的是能否立刻开始下载,而不苛求下载的文件大小与时间。
    • 用户打包的文件中,除了文档之外,如照片、视频、PDF文件可压缩尺寸的余地太小,压缩反倒徒劳。

    放弃压缩之后,拿到源文件的第一块内容之后就立刻可以生成打包文件了,省下CPU资源,省下等待时间。假设要求输出是ZIP包,无压缩的包仍是ZIP格式。

    对纠结用户下载流量大小的人,告诉你打包下载的内容是经过 gzip 再由浏览器解开的,你可以安心了。

    资源考虑:按流处理

    既然放弃压缩,省下的工作都是I/O,即搬运源文件内容到服务器,再以一个文件的形式搬运到用户手里,为什么中间要存一次文件呢?我们大可以把前后两个管道接上,拿到的每一块源文件内容都立即倒手给用户。

    我们只要明白并处理好

    • 无压缩的打包只是把源文件内容一个一个接起来,形成一个文件而已。
    • 文件下载起始需要一个预估大小,只能多,不能少。

    按流处理之后,无论源文件或打包产出文件都无需存在硬盘上了,剩下硬盘资源,剩下I/O时间。

    并发考虑:Golang实现web服务

    打包下载服务仍是请求响应模型的web服务。如何提高并发下载数?在资源紧张的情况下

    • 多进程较为消耗资源,考虑使用多线程或协程
    • I/O 为主,考虑使用贴近底层的语言
    • web 服务选简单些的框架,不需要 view、不需要 session、不需要关系数据模型

    结果 Golang 语言胜出,采用 Golang 内置的 http 库由 goroutine 支持并发。

    安全考虑:secure-token auth

    打包下载服务授予 key pair 给服务使用者,并约定共同的hash算法,由服务使用者发出请求前做签名生成 token,之后由打包下载服务验证签名,并进一步验证是否过期以反盗链。

    在打包下载服务这里实际只做了验证(authentication),而没有做授权(authorization)。没错,任何签名正确且未过期的请求都可以被放行。你应该注意到了,签名请求的是服务使用者,那是你的某个使用此服务的产品,不是最终用户,对最终用户做授权是各产品自己的事,这也是为独立性的考虑。

    独立可复用考虑:独立无状态服务

    服务中必须把独立做到极致,才易复用

    • 不耦合任何产品里的业务和代码
    • 不存储任何产品的数据
    • 容器化,把代码打成docker image,按需要的配置启动container
    • 只接受可直接访问的源文件地址,并在打包前发option请求尝试访问
    • auth算法是公开的,只有key pairs是服务自己存储的,前一节提到过

    浏览器支持考虑:小心处理

    这是些十分大众又出名的坑,小心处理就是。

    小心文件名
    • IE和现代浏览器对UTF8文件名的对待是有区分的,我们在服务端都满足它们。
    • 不同浏览器对特殊字符的转换也是有差异的,我们在服务里直接统一改好名,同时做到区别重名。
    别拼接源文件地址进 URL

    提出打包下载请求肯定要带着若干源文件的地址,发一个 POST ajax 请求比较理想。因为若选 GET 请求只能拼 URL,源文件地址的URL长度和数目未知,存在超过 URL 长度限制的可能(IE有2084个字节的限制)。
    POST 请求带回一个固定长度的存根,在回调方法里再组装一个下载打包文件的 URL,直接打开它就可以启动下载了。这两个请求的衔接是一个用户无法干预的连贯动作,所以时间很快,所以把存根存放在内存数据库如 Redis 中可以设一个很快的过期时间。

    高可用、高并发:集群方案

    看了前文,你该知道这个服务被设计成独立无状态的。和其他集群一样,一个负载均衡器就可实现。

    但是前文我说过是用 docker 部署的,我推荐在几个VM之间建 docker swarm,直接利用 docker 的 ingress 网络特性来做负载均衡,通过横向扩展 docker swarm 中节点数目和container数目来支持更高并发。

    存根依赖的内存数据库如 Redis 自然也需要做集群,负载很小,只为高可用。

    实现

    你很走运,我已实现好了,拿去吧~

    demo: http://zipper.demo.wushaobo.info
    github: https://github.com/wushaobo/zipper
    docker: https://hub.docker.com/r/wushaobo/zipper

    相关文章

      网友评论

          本文标题:打包下载服务

          本文链接:https://www.haomeiwen.com/subject/wshkuxtx.html