问题
问题大概出在18年的双十二期间,由于电商运营团队在做活动的时候,在几个活动页面展示了好几张特别大的图片(大概有800k-1M大小的样子), 致使用户在实际浏览过程中,体验并不是很好。而如果只是普通用户体验不好,也就罢了,偏偏那几天大BOSS关注起来了app,于是问起缘由来,一级一级问过来,问到了我这里。
我当然很明白这里面的问题所在,说白了就是由于你网站上的媒体资源加载太慢了,直接造成了用户体验差。而为什么你网站的媒体资源加载慢呢?那是因为你选择了在自有机房来托管这些媒体资源数据。而这些自有机房,在网络连接速度、稳定性等方面都与专业的云服务商没法比,这就间接导致了问题的产生。
选型
有了问题,就要有对应的策略。我于是首先负责了云存储的选型,当时面临的选择包括七牛云存储,腾讯云存储,阿里云储存这几家。当时与老大商量过,认为作为一款一多半流量来自于微信平台的app,当然还是应该选择一款腾讯旗下的云存储服务了,他对此表示赞同。但还是应该考虑价格因素,经过简单的计算,发现三家在价格上没有差多少。于是选型并不费事,直接就选用了腾讯云存储。
然而,我还需要根据我的选型,去做一个使用这些新服务的费用评估。这个费用评估报告,也会交给上级领导,最后由他们来拍板,是否使用腾讯云储存服务来作为媒体资源的提供者。
一切顺利,我的选型报告给领导看后,认为这个方案是可行的。一个预期的结果是,通过使用腾讯云存储方案,能够使app上的图片加载速度快将近10倍,这样的好事,为什么不做?
技术方案
摸着石头过河
我自己决定了技术方案,这个技术方案的核心是摸着石头过河。因为在当前的技术栈中,还从来没有使用过云存储,我们谁也不知道上云会有哪些风险,会产生多少费用,即便是有一些评估,但毕竟难以保证准确。
既然是摸着石头过河,我的第一步是先在app上做一个完全新的功能出来,这个新功能一定要用上云存储。这样做有几点好处,其一,数据是完全新的,没有任何历史数据,不用额外去做数据的迁移工作。其二,在做新功能的期间,也能够熟悉了解云存储接口和使用。
我选中了订单的评价功能的改进,来作为我使用腾讯云的第一步。由于历史的原因,以往的订单评价功能,只能给出文字评价以及好中差评,并不能带图片评价。之前,已经有业务部门的同事要求,希望能够实现带图评价。于是正好借助这个功能,练练手。事后来看,我这个思路非常好,这个功能做好后没多久,app所有的可用资源就都以腾讯云存储的数据方式展现了。
在这期间,由于后端语言使用的是python
,因此直接参考腾讯云存储的python sdk文档
是很快速高效的学习方法。通过简单的测试,可以很方便地对接口进行熟悉。
技术细节
本地数据和远程数据
从一开始我就不想完全放弃本地数据,也就是说,即便是我将所有的本地数据都迁移到了云上。我还是要在本地进行一次备份。数据的存储,删除,更新等操作,一定是先在本地完成,再同步到云上。这个同步的过程,为了快速,并不是说再执行一次迁移数据的脚本,而是说,将原有的操作,在云上再重新执行一遍。而实际客户端读取资源文件时,还是直接拉取云上的资源,而无须去获取本地资源。这里要再多说两句,最初我为了以防万一,写的读取资源的方法,原理是如果云上没有这个资源,就去读取本地资源
,但是经过实际测试,发现判断云上是否含有某个资源的方法(get_qcloud_object
)执行效率太低了,考虑到我当时已经将所有的数据完全进行了迁移,并且保证本地所有的操作,也会同步到云上,因此我可以放心地拼接一个可用的云存储链接,来作为我的图片资源地址来使用。这样以来,整个读取速度就上去了一大截。当然这样做还有一个好处,也就是容灾,我看到前不久,阿里云的北京机房还出了乱子,导致很多app处于不可用状态。我这个方案,当然能够很好地解决这个问题,一旦腾讯云上的资源处于不可访问的状态,我也可以快速地进行部署,然后将资源读取从腾讯云切换到本地机房服务器。
说到这里,其实已经很明白了。其实我的核心方法,只有三个。一个是上传数据的,用到的sdk里面的方法是client.put_object
这个方法,另外一个是删除数据,用到的sdk里面的方法是client.delete_object
,还有一个则是展示资源的方法,这个方法是完全自己写的,其原理也不过是将云上的资源url拼接好。
本地bucket与远程bucket
在实现腾讯云存储的时候,有一个bucket
的概念。其实按照我对于bucket
的理解,就是跟他的中文解释一样,是一个篮子
。这个篮子里面,什么东西都可以往里面装。在我最初的设计中,我考虑到要创建一个bucket
的时候,想到了直接创建一个名叫comment
的bucket
来存放商品评价图片。之所以这么做,也是因为我在本地也是想要创建一个comment
的bucket
,但是后来发现不能简单地将本地bucket
与远程bucket
一一映射。
这主要也是从两个方面来考虑。第一是数据的迁移,数据迁移的时候,其中一个配置项就是bucket
的名字,这个时候如果我将本地20几个bucket
一一迁移,就会显得很麻烦,每次都要修改配置文件,也要上传好多次。所以这个时候我开始考虑将原来的bucket
放到下一级去处理。这有点像是苏联的成立,原来的共和国本身也是国家,但是我让他成为苏联的加盟共和国,这样就能够方便管理。同样的道理,我这里只要对象存储上创建一个bucket
,由它来作为‘苏联’。这样就能够让我的数据迁移工作更简单。
方便的还不只是数据迁移,由于在腾讯云的对象存储中,每个bucket
需要单独配置。比如,跨域访问CORS设置,防盗链设置,回源设置,是否开启CDN设置等,这些操作我们不能通过API来完成,必须要通过工程师手动配置。而如果有多个bucket
的话,那么配置起来就会很麻烦。
而我最后虽然建立了一个苏联,但是也只不过是多写了两行代码,将原来上传资源,删除资源,展示资源的方法又修改了一下,重新做了映射。一起都非常简单,就能够解决我的难题。
当然,我们也是需要考虑每个bucket
的最大存储空间和存储资源数量,我后来调查了一下。发现我的数据量,完全可以将原来的多个bucket
合并成一个,而无需担心这个问题。
迁移数据
迁移数据乍一看是个麻烦事,我开始有几点考虑。一是整个bucket
的数据量,达到了几十GB。当时考虑如果在网络情况不好的情况下,一天的时间也未必能够完成数据的全部迁移。有这个担忧并不是毫无道理,我最先想到的其实是当前用迅雷下片的体验,几个G的资源都要下载几个小时,更不要说是几十GB的大量碎片文件了。但是后来发现,我本地网速还不错,上传的速度够快,至少是比我想象中要快。我的第二点考虑,虽然数据迁移速度很快,但是由于在迁移工作开展之前,并没有对哪些数据需要迁移
进行分析,致使有很长一段时间,我都看着terminal
窗口中在上传一些日志文件、发票文件。而这些文件,用户是很少有感知的,其实也并不是我此次工作想要解决的问题。
这个时候,就看出腾讯云这个数据迁移工具的可配置性了。
在本地迁移中,有两个设置,一个是用来设置本地数据迁移的包含目录,一个则是需要排除的目录的。
localPath=/opt/demo/var/buckets
exeludes=/opt/demo/var/buckets/log;/opt/ljmall-public--2/var/buckets/invoice
通过上面的配置,我就可以将占用大量内存空间的日志文件,发票(主要是pdf)文件排除出去,节省了云上大量的存储空间。
由于对配置文件进行了修改,也加上网络情况良好。实际上,整个数据迁移的过程不仅快速而且也很完美。并没有出现丢失数据的情况,这里需要补充一点,由于腾讯云存储数据迁移工具是支持日志记录的,因此所有已上传成功的文件信息都已经记录在日志中,这样也避免了重复上传等问题。所以,虽然我在中途修改了两次配置文件,但并不影响我最后将我需要上传的资源快速高效地上传上去。
由于在迁移数据之前,几乎所有的图片资源都是由运营人员产生的,所以我特地选择了一个运营不上班的周末来做这件事。之后,只需要在迁移数据完成之后,将原来写的上传、删除、展示资源的方法启用,之后所有的数据就能够实现同步了。当然,为了做的更好一点,也是可以每隔一段时间,定期执行迁移脚本,来保持数据的同步的。只是,从我目前的观察来看,无需定时迁移的脚本已经能够满足业务的需要了。
前端资源的处理和上云
本来我做这个功能的时候,最初其实只是想把图片等媒体资源进行迁移,这样能够确保用户在访问媒体资源的时候,不至于因为网络情况差,造成打开图片卡顿,半天出不来一张完整的图片。然而后来,我仔细想这个问题。所谓的对象存储,理论上来说,只要是静态资源文件,就都可以用上这个方案。因此,后来,我也将微商城的前端资源放到了云上。
这些前端资源是由webpack生成的一些js,css等文件,也包括由url-loader生成的一些图片资源,字体资源等。我使用到了一个开源的前端工具库来帮我自动完成build之后的上传操作,这个工具库是cos-webpack 。
这里的一切也很简单,不过这个工具库,让我觉得有一点不舒服的是,他只会负责将生成的文件上传到云上,但是却不会负责将云上原有的资源删除掉,这样一旦新的前端资源上传上去了,部署了新的前端代码。那么那些老旧的前端资源就失去了意义,再也不会有他们的用武之地。我并没有对这个前端工具库进行修改,直接使用了。当然,这样的设计也有好处,因为我们build新的前端资源的时机有可能与我们下一次部署服务的时机并不相同,这个时候如果说build之后,就将原有的资源删除掉,那么就会直接导致线上的某些页面,甚至所有页面处于不可访问状态。这显然不是我们希望得到的结果。
也许有一天,我会对之进行修改,做一个更好的工具,来处理前端资源。
顺便多说一句,我将前端资源单独放到了一个叫做fe
的bucket
里面。这样,实际上,我整个项目完成之后,一共是有两个bucket
,也能够很好地应付业务,逻辑清晰。
智能压缩
在使用COS来作为前端资源存放地的时候,还发现,如果不应用CDN的话,我没有办法做到资源的压缩。我们知道,包括js,css等资源,通过gzip压缩之后,能够节约一半以上的体积。对于chrome等浏览器来说,就意味着传输效率快了一倍以上。在没有应用COS之前,我们通过本地服务器上的nginx
来实现gzip。相关原理,点这里:探索HTTP传输中gzip压缩的秘密。但是如果直接使用上传到COS上的资源链接,正常加载的时候,是不能进行智能压缩的。查看文档才知道,只有配合了CDN之后,我才能够实现智能压缩。但是仅仅启用了CDN并没有什么作用,后来我为此还专门发了工单提问,后来终于在腾讯云工程师的帮助下解决了。原来,我虽然启用了CDN,但是我没有相应地将对外展示的资源url进行修改,用户访问的其实还是对象存储北京机房里面的数据。后来,当我将url重新拼接,换成cdn的链接后,智能压缩才应用上。
CDN的设置
在使用过程中,由于没有或对CDN设置得有问题,导致CDN上的资源加载也出了问题。
需要说明一下的是,当以COS源的方式接入CDN的时候,在对象存储上的bucket
是与CDN上的域名
一一对应起来的。正式因为有这样的对应关系,我又要说了,我建立的那个苏联是多么的有必要。只需要一个苏联去跟美国单独建立外交关系,而无需各个加盟共和国单独去建立外交关系,省了好多事。
我最初设置了一个IP访问限频配置,当时设置的时候也没有在意。后来过了几天,发现有时候,资源的访问会出现514 错误,经过查询才发现原来是这个设置出的问题,又赶紧把这个设置放宽到了一个比较大的标准上。
另外,前面提到的fe
这个bucket
也出了问题,有跨域的问题,后来我把HTTP Header配置进行了设置,问题才得以解决。
当然,前面提到的智能压缩,在默认情况下也是关闭的,需要手动打开。
效果和理解
开始的时候,我虽然考虑到了要使用腾讯云的对象存储(COS)服务,也想按照大众的做法用上CDN,但是坦白讲,那个时候我对CDN还没有一个特别清晰的理解。我当然知道他是内容分发网络,能够让资源分配到各个节点,让全国各地,甚至全球各地的用户在获取资源时,都能够享受到一个比较快的速度。我有这样一个理解,但是还是很难将他与对象存储之间的关系说明白,讲透彻。
后来真正开始使用对象存储和CDN了。有那么几天,也通过腾讯云的后台,观测数据的变化,渐渐得也观察出来点门道。且听我一一道来。
我最初只用了对象存储,因此资源的访问地址就是
bucketname-appid.cos.region-code.myqcloud.com/key
如果上面的参照不好理解,可以举个例子,比如:
test-125777777.cos.ap-beijing.myqcloud.com/test.jpg
由于我访问资源的时候,已经指定了资源是在ap-beijing这个机房,因此全国各地的用户访问app进而访问到这个资源,都会从腾讯云北京机房获取这个资源。需要说明的是,我们的本地服务器的所在地也是在北京,但是由于我们本地服务器在网络延迟,响应速度方面还是与腾讯云有较大差距。从实际体验来看,只使用对象存储而不使用CDN已经对app中资源的加载速度有很大的改善了。(实际经过统计发现,有60%的流量是来自于北京,外地的流量不足一半)
之所以,我们还要使用CDN,一方面当然是希望能够越快越好,尽善尽美,这也是我作为一个开发者的追求。由于我身处北京,很难去测试外地用户在直接访问源站的时候的速度,但是我却能够通过CDN 的后台看到,全国各地,访问资源,普遍的延时在200ms以内,这对我来说,是可以接受的。另外一方面,其实从整体费用上来看,使用CDN并没有增加多少费用,没有使用CDN之前,费用的大部分来自于COS外网下行流量费,应用上CDN之后,由于用户可以访问分布在全国各地的各个节点来获取资源,因此对于COS来说,它的外网下行流量没有了。取而代之的是CDN流量,以及CDN回源流量。这里有必要再解释一下CDN回源流量,我最初还没有应用上腾讯云的时候,也不是很能理解CDN回源流量,当时在做费用评估的时候,将外网下行流量=CDN回源流量来计算。
实际上,按照我的理解,应该有这样的公式。在未使用CDN之前,
总流量 = 外网下行流量
在使用CDN之后,
总流量 = CDN流量 + CDN回源流量
当然我这里必须有一个前提,那就是COS(对象存储)的外部读取完全基于CDN。
通过上面两个公式,我们可以得出一个简单的结论,由于使用了CDN,外网下行流量没有了,由于业务的情况,总流量约等于CDN流量。
既然总流量约等于CDN流量,那么这个性价比就可以说非常高了。
后话
完成这个工作之后,被运营人员夸奖,说是自己家的app已经赶上天猫的速度了,听到这个话,还是很开心的,也不枉我这些天的努力。