起因
在知乎看到的问答,下边的淘系的回答
阿里巴巴淘系技术的答案
淘宝的图片访问,有 98% 的流量都走了 CDN 缓存。只有 2% 会回源到源站,节省了大量的服务器资源
但是,如果在用户访问高峰期,图片内容大批量发生变化,大量用户的访问就会穿透 cdn,对源站造成巨大的压力
CDN工作原理
内容分发网络(Content Delivery Network,简称CDN)是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络
CDN 应用广泛,支持多种行业、多种场景内容加速,例如:图片小文件、大文件下载、视音频点播、直播流媒体、全站加速、安全加速
借用阿里云官网的例子,来简单介绍 CDN 的工作原理
假设通过 CDN 加速的域名为 www.a.com,接入 CDN 网络,开始使用加速服务后,当终端用户(北京)发起 HTTP 请求时,处理流程如下:
- 当终端用户(北京)向 www.a.com 下的指定资源发起请求时,首先向 LDNS(本地DNS)发起域名解析请求
- LDNS 检查缓存中是否有 www.a.com 的 IP 地址记录。如果有,则直接返回给终端用户;如果没有,则向授权 DNS 查询
- 当授权 DNS 解析 www.a.com 时,返回域名CNAME www.a.tbcdn.com 对应 IP 地址
- 域名解析请求发送至阿里云 DNS 调度系统,并为请求分配最佳节点 IP 地址
- LDNS 获取 DNS 返回的解析 IP 地址
- 用户获取解析 IP 地址
- 用户向获取的 IP 地址发起对该资源的访问请求。如果该 IP 地址对应的节点已缓存该资源,则会将数据直接返回给用户
例如,图中步骤 7 和 8,请求结束。如果该 IP 地址对应的节点未缓存该资源,则节点向源站发起对该资源的请求。获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点,例如,图中的北京节点,并返回给用户,请求结束
从这个例子可以了解到:
- CDN 的加速资源是跟域名绑定的
- 通过域名访问资源,首先是通过 DNS 分查找离用户最近的 CDN 节点(边缘服务器)的 IP
- 通过 IP 访问实际资源时,如果 CDN 上并没有缓存资源,则会到源站请求资源,并缓存到 CDN 节点上,这样,用户下一次访问时,该 CDN 节点就会有对应资源的缓存了
淘宝鹿班图片业务背景
商品的主图贯穿整个导购和交易链路,相比文字,图片更能吸引眼球,主图对消费者的购物决策有很大的影响
主图上表达的内容各式各样,但其中一定少不了的一定是价格的表达
长期以来,主图上的价格表达都是商家自己维护,商品价格发生变化后,手动去换图。这样做,会带来3个问题:
- 价格的准确性:商家手动填写的图片价格,跟实际的购买价可能不一致,造成不好的用户体验
- 价格更新的及时性:有时候,由于优惠券/品类券的生效失效,会导致商品的价格变化会很频繁,商家根本来不及换图
- 商家的操作成本:手动修改图片的价格,成本还是很高的,需要通过 ps 等软件修改图片,重新上传,编辑商品
今年双11,我们淘宝鹿班团队,试图通过技术手段来解决这些问题。当商品价格发生变化后,系统自动计算新的价格,自动合成图片,然后更新商品主图。我们知道,淘宝网有上亿的商品,光大促商品就有几千万,因此,价格变化导致的图片变化频率非常高。最高的就是在双 11 的 0 点,全部大促商品的价格都会由日常价格变成大促价格。这就意味着,大促高峰期,有上千万的图片刚生成就会被用户访问。那这个情况会产生什么问题呢,让我们先了解下淘宝的图片空间和 CDN 的架构,就清楚了
淘宝图片空间和CDN的架构
淘宝整个图片的访问链路有三级缓存(客户端本地、CDN L1、CDN L2),所有图片都持久化的存储到 OSS 中。真正处理图片的是 img-picasso 系统,它的功能比较复杂,包括从 OSS 读取文件,对图片尺寸进行缩放,编解码,所以机器成本比较高
CDN 的缓存分成 2 级,合理的分配 L1 和 L2 的比例,一方面,可以通过一致性 hash 的手段,在同等资源的情况下,缓存更多内容,提升整体缓存命中率;另一方面,可以平衡计算和 IO,充分利用不同配置的机器的能力
用户访问图片的过程如下
- 用户通过手机淘宝来搜索商品或者查看宝贝详情
- 详情/搜索/推荐通过调用商品中心返回商品的图片 URL
- 客户端本地如果有该图片的缓存,则直接渲染图片,否则执行下一步
- 从 CDN L1 回源图片,如果 L1 有该图片的缓存,则客户端渲染图片,同时缓存到本地,如果 L1 没有缓存,则执行下一步
- 从 CDN L2 回源图片,如果 L2 有该图片的缓存,则客户端渲染图片,同时 CDN L1 及客户端缓存图片内容,如果 CDN L2 没有缓存该图片,则执行下一步
- 从图片空间回源图片,图片空间会从 OSS 拉取图片源文件,按要求进行尺寸缩放,然后执行编解码,返回客户端能够支持的图片内容,之后客户端就可以渲染图片,同时 CDN 的 L1、L2 以及客户端都会缓存图片内容
频繁换图带来的技术挑战
当商品的价格发生变化时,我们会使用新的价格重新合成图片,更新商品中心中存储的图片 URL
这样会带来 2 个问题:
- CDN 及手机淘宝原本缓存的图片内容失效了,用户访问图片会全部回源到 img-picasso
- 由于更改了商品的字段,交易的核心应用(购物车和商品中心)的缓存也失效了,用户浏览及购物时,对商品的访问会走到 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 个办法
- 改图保持图片 URL 不变,可以避免商品链路的缓存失效
- 在访问高峰到来之前,提前预热图片到 CDN,可以避免 CDN 缓存失效对源站的压力
下面,介绍下我们具体是怎么做到这2点的。
频繁换图的应对方案——改图保持图片URL不变
图片内容发生变化时,执行下面2个操作:
- 更新 OSS 内容:使用新的图片内容替换 OSS 中老的图片内容
- 刷新 CDN 缓存:清除 CDN 之前缓存的图片内容这样,用户再次访问图片时,发现 CDN 没有缓存,就会回源到 img-picasso,从 OSS 拉取新的图片内容。由于图片 URL 没有变化,就不必去更新商品中心的图片链接,这样商品链路的缓存可以保持不变
在真正实施这个方案的过程中,遇到了几个问题,简单跟大家分享下
-
OSS三地同步
淘宝的图片空间,承载了淘系所有图片的上下行稳定性保障,为了保障高可用,一份资源会存储到三地 OSS。图片上传时,默认只上传一地,利用 OSS 的能力,自动同步到另外两地
但是使用 URL 不变方案,CDN 缓存已经清除完成后,如果另外 2 地的 OSS 还未同步完成,用户访问后,就会回源到旧的图片内容,发现图片内容没有变化
针对该问题,我们将异步同步 OSS 软链的模式,改成三地同步建软链,三地都返回成功后,再去清除 CDN 缓存,这就保证了用户访问的图片一定是最新的内容
-
图片尺寸收敛
同一张商品图片会用于不同的场景坑位展现,不同的坑位对图片的尺寸有不同的要求。为此,图片空间提供了一项功能,可以方便的生成不同尺寸的缩率图。只需要访问图片时,给图片增加不同的后缀,img-picasso 源站就可以按要求进行图片进行缩放
由于历史原因,之前对缩放的尺寸种类没有限制,导致 CDN 上的图片后缀格式多达 2400种+,TOP6 格式覆盖率 46%,TOP15 格式覆盖率 64%。这意味着,一张图片,在 cdn 上最多可能有 2400+ 个不同的 url,当图片内容变化后,要把这些缓存全部清掉,才能保证所有用户看到的图片都是新内容
为了解决这个问题,我们对域名格式进行了收敛
图片空间对于图片质量压缩参数的规则如下:
- 图片质量参数常见有一下8种形式:Q90、Q75、Q50、Q30、q90、q75、q50、q30
- 图片锐化参数常见有一下3种形式:s100,s150,s200
我们重新将图片质量定义为高质量图片和低质量图片,收敛格式为 q90 和 p50s150 这样,就可以把 2000 多种格式收敛到 6 种主要格式,CDN 清除缓存才变得可行
- 多副本清除CDN缓存
通过图片尺寸收敛,每张图片只需要清除 6 个不同的 url 就可以了,那能不能进一步提升刷新效率呢?
为此,阿里云 CDN 为我们提供了多副本刷新的解决方案:每种不同后缀的图片,作为图片的一个副本,在 CDN 的 swift 层增加一层 KV 结构,存储 url 和不同副本的映射关系,清除缓存时,可以通过该结构找到所有副本,实现快速清除所有副本。这样,每张图片,我们只需要调用一次 CDN 清除缓存接口就可以了,极大提升了 CDN 缓存刷新效率
- 图片域名收敛
淘系的图片域名有 300 多种,主要有下面2个原因:
- 图片完整的链接太长,所以存储时经常只存最后一段,业务自己来拼域名,很多业务就自己申请了一个图片域名来拼
- PC 时代,浏览器对同一域名下的并发请求数是有限制的,不同浏览器不一样,一般 6 个左右
为了突破该限制,一些业务就会申请多个域名,随机的拼不同的域名。前面我们讲过,CDN 的缓存是跟域名绑定的,不管是缓存命中还是缓存清除,都只能针对一个域名
我们显然不可能改一张图,就去对 300 个域名调用 CDN 刷新。于是我们考虑对图片域名进行收敛,使得用户对图片的访问都路由到同一个域名,我们希望将所有的图片访问统一收敛到 http://picasso.alicdn.com,具体实现方式如下:
- 对于手淘和猫客客户端,图片访问都收口在图片库,我们推进图片库进行改造,符合一定规则的 url,统一收敛到http://picasso.alicdn.com,实现了域名的一刀切
- 对于PC浏览器端,就比较麻烦了,没有统一收口的地方。我们只能退而求其次,针对访问最多的 6 大域名,在 cdn 上配置域名转发规则,重定向到 picasso 域名
通过这种方式,我们实现了全网 99% 以上的图片访问流量都路由到 picasso 域名,图片内容发生变化时,通过清除 picasso 域名的 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,可以实现图片切换瞬间生效,同时对源站没有压力
具体方案如下:
- 提前合成多波段图片:我们知道大促期间商家集中换图的时间点后,按这些时间点把图片的展示分成多个波段,每个波段图片提前合成,并提前将图片 URL 写入到商品中心扩展结构中
- 图片访问路由:营销系统根据配置的大促氛围切换计划,告诉鹿班图片二方包,当前是哪个波段,鹿班根据当前波段及场景,返回正确的图片 URL 给各个场景
- 图片渲染:各个场景拿到图片 URL 后,结合自身的业务逻辑,决定是否要展现该图片
- CDN 图片预热:为了避免图片集中切换时,把源站击垮,我们会在集中切换前把这些冷图片内容预热到 CDN
- 波段内图片变化:提前合成各个波段图片后,商家可能会临时发券/改价,导致商品价格再次变化,对于这类换图需求,为了避免更新商品中心的图片 URL,我们通过前边刷 CDN 缓存的方式实现
总结和展望
CDN 技术广泛应用于互联网的各个场景,如今的 CDN 服务商,都提供了非常简单的业务接入方式,而且 CDN 的费用每年都在降低,这一切使得 CDN 的接入和使用成本越来越低
本文通过淘宝图片业务的例子,为大家阐述了使用 CDN 过程中可能遇到的问题和解决思路
淘宝的图片业务除了访问量大,还会面临更新频繁的问题。图片的频繁更新,一方面会由于商品上的图片 url 变化,导致商品缓存失效,另一方面会大幅降低 CDN 的图片访问缓存命中率
针对图片 url 变化导致商品缓存失效的问题,我们通过刷新 cdn 缓存,用户访问时重新回源的方式,实现了改图保持图片 url 不变,这个过程中了,我们解决了一些列的问题,包括:OSS 三地同步更新、图片尺寸收敛、图片域名收敛、客户端及浏览器本地缓存
针对改图降 CDN 图片缓存命中率的问题,我们根据业务的特点,提前合成不同波段的图片,并预热到 CDN,保障了源站的安全
目前,淘宝上用户看到的图片,都是提前合成好的。未来,我们考虑在用户访问图片时,实时合成图片。通过这项技术,可以实时感知业务更多的实时信息,可以根据这些信息,在图片上合成当前用户或者环境更匹配的文案/元素等内容,给用户带来更多的惊喜
当然,实时合图也会面临更多的挑战,如:计算能力、合图性能。此外,对于 CDN 而言,由于每次用户访问的内容是临时合成的,CDN 的缓存策略也是一个很大的挑战