CDN(Content Delivery Network)内容分发网络。
网站应用,静态资源占流量的多数。系统做了动静分离之后,就可以把静态资源通过CDN加速。这样,静态资源的请求大部分通过就近部署的CDN服务器提供服务,用户的延迟也会有明显的提升。网站服务器专注于服务动态流量,带宽压力会小很多。
动静分离,部署时静态资源要给一个单独域名,这个域名是个CNAME(cname解析成另一个域名,而不是IP),cname映射到CDN服务厂商提供的DNS服务器,CDN DNS 服务器会根据请求的IP地址所在区域和资源内容,返回就近的CDN缓存服务器ip,后续用户对这个DNS的请求都会转到这个IP上来。
可以利用CDN加速,我认为是动静分离最大的意义。这对于大型网站尤其必要,但对于非公有云网站应用,如果流量没那么大,静态资源的量不是很大(相对于知名的大厂可以做比较),那没有任何必要用CDN,也没有必要动静分离部署。非公有云网站,有一种情况可以考虑CDN:将需要安装到前端的体积比较大的客户端放到CDN,用户下载这个客户端可以选择从CDN下载,这样来解决下载大客户端的性能问题。
借用阿里云官网的例子,来简单介绍CDN的工作原理。
假设通过CDN加速的域名为www.a.com,接入CDN网络,开始使用加速服务后,当终端用户(北京)发起HTTP请求时,处理流程如下:
1.当终端用户(北京)向www.a.com下的指定资源发起请求时,首先向LDNS(本地DNS)发起域名解析请求。
2.LDNS检查缓存中是否有www.a.com的IP地址记录。如果有,则直接返回给终端用户;如果没有,则向授权DNS查询。
3.当授权DNS解析www.a.com时,返回域名CNAME www.a.tbcdn.com对应IP地址。
4.域名解析请求发送至阿里云DNS调度系统,并为请求分配最佳节点IP地址。
5.LDNS获取DNS返回的解析IP地址。
6.用户获取解析IP地址。
7.用户向获取的IP地址发起对该资源的访问请求。
如果该IP地址对应的节点已缓存该资源,则会将数据直接返回给用户,例如,图中步骤7、8,请求结束。
如果该IP地址对应的节点未缓存该资源,则节点向源站发起对该资源的请求。获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点,例如,图中的北京节点,并返回给用户,请求结束。
从这个例子可以了解到:
(1)CDN的加速资源是跟域名绑定的。
(2)通过域名访问资源,首先是通过DNS分查找离用户最近的CDN节点(边缘服务器)的IP
(3)通过IP访问实际资源时,如果CDN上并没有缓存资源,则会到源站请求资源,并缓存到CDN节点上,这样,用户下一次访问时,该CDN节点就会有对应资源的缓存了。
以淘宝商品图片数据为例,长期以来,主图上的价格表达都是商家自己维护,商品价格发生变化后,手动去换图。这样做,会带来3个问题:
(1)价格的准确性:商家手动填写的图片价格,跟实际的购买价可能不一致,造成不好的用户体验。
(2)价格更新的及时性:有时候,由于优惠券/品类券的生效失效,会导致商品的价格变化会很频繁,商家根本来不及换图。
(3)商家的操作成本:手动修改图片的价格,成本还是很高的,需要通过ps等软件修改图片,重新上传,编辑商品。
今年双11,淘宝鹿班团队,试图通过技术手段来解决这些问题。当商品价格发生变化后,系统自动计算新的价格,自动合成图片,然后更新商品主图。
我们知道,淘宝网有上亿的商品,光大促商品就有几千万,因此,价格变化导致的图片变化频率非常高。最高的就是在双11的0点,全部大促商品的价格都会由日常价格变成大促价格。
这就意味着,大促高峰期,有上千万的图片刚生成就会被用户访问。那这个情况会产生什么问题呢,让我们先了解下淘宝的图片空间和CDN的架构,就清楚了。
淘宝图片空间和CDN的架构
淘宝整个图片的访问链路有三级缓存(客户端本地、CDN L1、CDN L2),所有图片都持久化的存储到OSS中。真正处理图片的是img-picasso系统,它的功能比较复杂,包括从OSS读取文件,对图片尺寸进行缩放,编解码,所以机器成本比较高。
CDN的缓存分成2级,合理的分配L1和L2的比例,一方面,可以通过一致性hash的手段,在同等资源的情况下,缓存更多内容,提升整体缓存命中率;(这里我觉得是不是用redis类似的指派槽会更好一些呢,这样才可以根据机器性能来进行配置)另一方面,可以平衡计算和IO,充分利用不同配置的机器的能力。
用户访问图片的过程如下:
(1)用户通过手机淘宝来搜索商品或者查看宝贝详情。
(2)详情/搜索/推荐通过调用商品中心返回商品的图片URL。
(3)客户端本地如果有该图片的缓存,则直接渲染图片,否则执行下一步。
(4)从CDN L1回源图片,如果L1有该图片的缓存,则客户端渲染图片,同时缓存到本地,如果L1没有缓存,则执行下一步。
(5)从CDN L2回源图片,如果L2有该图片的缓存,则客户端渲染图片,同时CDN L1及客户端缓存图片内容,如果CDN L2没有缓存该图片,则执行下一步。
(6)从图片空间回源图片,图片空间会从OSS拉取图片源文件,按要求进行尺寸缩放,然后执行编解码,返回客户端能够支持的图片内容,之后客户端就可以渲染图片,同时CDN的L1、L2以及客户端都会缓存图片内容。
频繁换图带来的技术挑战
当商品的价格发生变化时,我们会使用新的价格重新合成图片,更新商品中心中存储的图片URL。这样会带来2个问题:
(1)CDN及手机淘宝原本缓存的图片内容失效了,用户访问图片会全部回源到源站img-picasso。
(2)由于更改了商品的字段,交易的核心应用(购物车和商品中心)的缓存也失效了,用户浏览及购物时,对商品的访问会走到db。
源站img-picasso处理图片,以及查询商品DB,都是非常消耗资源的。CDN及商品的缓存命中率降低后,对源站img-picsasso以及db会产生巨大的压力。
拿CDN缓存为例,简单计算一下,CDN平时的命中率是98%,假设命中率降低1个点,对源站的压力就会增加1/3(原本承担2%的流量,现在需要承担3%的流量),意味着img-picasso需要扩容1/3。如果全网一半的图片都同时变化,cdn的命中率降到50%,对img-picasso的访问量就会增加25倍,这个扩容成本肯定没法接受。
解决这2个问题,对应的有2个办法:
(1)改图保持图片URL不变,可以避免商品链路的缓存失效。
(2)在访问高峰到来之前,提前预热图片到CDN,可以避免CDN缓存失效对源站的压力。
频繁换图的应对方案
1.改图保持图片URL不变
图片内容发生变化时,执行下面2个操作:
(1)更新OSS内容:使用新的图片内容替换OSS中老的图片内容
(2)刷新CDN缓存:清除CDN之前缓存的图片内容
这样,用户再次访问图片时,发现CDN没有缓存,就会回源到img-picasso,从OSS拉取新的图片内容。
由于图片URL没有变化,就不必去更新商品中心的图片链接,这样商品链路的缓存可以保持不变
在真正实施这个方案的过程中,遇到了几个问题,简单跟大家分享下:
OSS三地同步
淘宝的图片空间,承载了淘系所有图片的上下行稳定性保障,为了保障高可用,一份资源会存储到三地OSS。图片上传时,默认只上传一地,利用OSS的能力,自动同步到另外两地。
但是使用URL不变方案,CDN缓存已经清除完成后,如果另外2地的OSS还未同步完成,用户访问后,就会回源到旧的图片内容,发现图片内容没有变化。
针对该问题,我们将异步同步OSS软链的模式,改成三地同步建软链,三地都返回成功后,再去清除CDN缓存,这就保证了用户访问的图片一定是最新的内容。(数据一致性保证)
客户端及浏览器缓存
通过多副本和图片域名收敛,cdn的缓存问题得到了解决。但在cdn之上,用户图片访问首先是来自客户端或者浏览器,这里也会有一层缓存。
大家知道,浏览器的缓存都遵循标准的http max-age协议,指定该header后,到了时间图片就会失效,访问到新的图片。所以我们可以在源站img-picasso回源给cdn时,添加max-age协议头,值为1分钟,cdn会原封不动的透给浏览器,这样浏览器就可以实现1分钟内图片缓存失效,重新到cdn拉新的图片资源。
对于手机淘宝客户端,我们在原有的LRU缓存机制之上,另外支持标准的http协议。这样,手机淘宝也实现了1分钟内图片缓存失效。
提前预热CDN图片
通过改图保持图片URL不变,我们解决了改图对商品链路缓存的影响。但是,图片变化时,虽然URL没有变,但我们清除了CDN缓存,导致用户访问时还是会回源到img-picasso源站,所以对图片源站的压力依然存在。
我们发现,商品的价格变化大部分发生在大促节奏变化的时刻,基于这个特点,我们通过提前合成图片,提前预热到CDN,可以实现图片切换瞬间生效,同时对源站没有压力。具体方案如下:
(1)提前合成多波段图片:我们知道大促期间商家集中换图的时间点后,按这些时间点把图片的展示分成多个波段,每个波段图片提前合成,并提前将图片URL写入到商品中心扩展结构中。
(2)图片访问路由:营销系统根据配置的大促氛围切换计划,告诉鹿班图片二方包,当前是哪个波段,鹿班根据当前波段及场景,返回正确的图片URL给各个场景。
(3)图片渲染:各个场景拿到图片URL后,结合自身的业务逻辑,决定是否要展现该图片。
(4)CDN图片预热:为了避免图片集中切换时,把源站击垮,我们会在集中切换前把这些冷图片内容预热到CDN。
(5)波段内图片变化:提前合成各个波段图片后,商家可能会临时发券/改价,导致商品价格再次变化,对于这类换图需求,为了避免更新商品中心的图片URL,我们通过本文上一章节刷CDN缓存的方式实现。