本文分享来自淘系内容前端团队-蒂夫
在开始正题以前,我先讲一个内容详情的业务场景和其面临的性能问题前端
图文内容详情业务自己有三个比较大的特色:web
那咱们看这么一个页面的加载和渲染过程大概是什么样的?算法
从上面的流程能够看出几个问题:小程序
这时候咱们要结合业务自己的特性进行一些大胆的思考:浏览器
首先想到的是在研发过程当中就对内容进行预渲染并存储起来,可是这个方案很快被 pass 了,有两个主要缘由:缓存
既然数据绝大部分是静态化的,为何不能把用户访问时静态数据和代码渲染后的结果进行静态化,这样不是省去了 renderToHTML 的过程了吗?,以下图所示安全
那讲到这里,咱们首先想到的是经过 SSR renderToHTMLString,而后把渲染后的结果进行缓存,这样访问到相同内容的请求能够直接将缓存结果返回,那这样有什么好处呢:服务器
可是同时也带来了其余的问题:markdown
这时候咱们就在想,若是可以将渲染的结果或者渲染的过程放在 CDN 上就行了,由于 CDN 节点比较多,而且在世界范围部署普遍,因此咱们尝试着将 SSR 渲染的结果存储在 CDN 上,可是随之带来另一些问题:weex
这时咱们正好了解到了 CDN 正在推广一种边缘计算的能力(EdgeRoutine,下面会作简单的介绍),简单点理解就是能够在 CDN 请求返回结果以前加上你的自定义脚本,而且能够访问 CDN 的数据,那就意味着咱们能够控制 CDN 请求返回的内容或者HTTP 状态,好像基本可以解决我上面说的两个问题,因此按照当前的技术能力和咱们的需求咱们针对请求链路进行了改造:
具体的降级和缓存清除的逻辑没有画出来,由于那是解决安全生产的问题,我主要想强调方案调整带来的性能提高。
因此从上图能够看出,一个正常的请求首先会请求到 CDN,CDN 若是发现缓存中没有的话会回源到 SSR 服务器,这样首屏其实只须要一个网络请求,有效的提高的首屏性能和下降了服务器压力。细心的你会发现页面首屏后还进行了一次请求动态数据的动做,由于还有一个对实时性要求比较高的数据须要展现给用户,可是并不影响用户浏览,另外虽然内容不怎么会更新但也会存在更新的状况,因此咱们会在浏览器端作一次缓存的时间和内容最新更新时间的对比,若是发现不一致,会主动作缓存更新,这样,既保证了性能,又避免了缓存过时
经过作如上的方案咱们在性能,业务指标提高,服务器压力上都有很大的收获
关于边缘计算,你们能够参考:developer.aliyun.com/article/757…
本篇文章贴出几张核心的图供你们参考:
简单总结就是咱们能够在 CDN 返回结果以前进行一些逻辑计算,而且这部分代码兼容 ES6 的规范,而且能够经过 HTTP 和外界服务进行沟通,达到有效的控制的 CDN 返回的表现的目的
在此我想重点介绍下边缘计算的共享优点,对于边缘计算来讲,它不只能够处理一些逻辑计算,还能够将计算的结果进行存储,存储能力是 Swift 的 Open API ,实现数据的 KV 存储,这就意味着,这个存储空间能够很是大。说道这里你们可能会感受比较抽象,能够看下面这张图,上面是指咱们正常的网络请求,用户手机直连数据服务器和页面 CDN,这就意味每一个人都要经历加载页面,加载数据,渲染页面等逻辑。下面是指 CDN ER 作了一层代理,这就意味着用户手机连接 CDN,CDN 负责和数据服务器和页面 CDN 进行沟通。那样这样有什么好处呢,这就意味着咱们能够将像内容详情这种数据或者渲染的结果直接存储在 CDN 上,而且不用担忧存储内容太多影响性能,这就就像一群人公用一部手机,你看完传递给下一我的刷新看相同的内容
既然能在 CDN 的 ER 节点上写 ES6 的代码,而且能够请求数据,这就意味着咱们能够在ER上执行不少逻辑,在这里我整理一些经常使用的:
那基于这些能力咱们还能支持哪些合适的场景落地呢,因此咱们针对淘系的场景进行了调研
总体调研有一个统一的思路,就是要找适合静态化的高流量场景,就是说页面是否有可被缓存的数据或者渲染结果,为此咱们整理了一个简单的表格:
接下来我作一些简单的说明:
总结一下标准的页面请求过程以下:
这里说一下,其实在数据侧有不少静态化策略已经被用的游刃有余,例如借助于 CDN、Tair、OSS,若是咱们可以让静态化的过程变得更加简单和通用,例如将数据或者页面渲染结果直接存储在 CDN,下次请求就能够直接复用渲染结果,有没有可能变成以下模式:
其实就是两个原则:
最终结合 ER 的能力和咱们的业务场景,咱们抽象为如下四种:
通过咱们的测试和实践,针对前三种产出了一些性能报告也能够分享给你们,虽然不全面,可是能说明问题,因为测试的页面场景不同,因此相互(数据预加载、 ESR、SSR 静态化)没有必要做对比,如下指标是相对没有使用 TESI 的能力进行的对比
虽然 ER 有这么多优点,可是接入成本仍是比较大的,例如要注意 ER 容器自己的各类限制、调试成本、云资源申请成本等,因此咱们须要提供一种标准的接入方式,初步了解到 W3C 有一个 ESI 的标准,维基百科介绍以下:
简单翻译一下:ESI 是一种边缘级 web 动态化的小型标记语言。ESI 的目的是解决 web 基础设施的扩展问题。它是边缘计算的一种应用方案
原理如图:
其实就是说,能够经过标签注入的方式,实现动静内容混合混合输出,比较符合咱们的诉求,而且其在语法上也比较丰富
但问题是 ESI 是一种 XML 的标准,阿里有不少页面资源类型并非 HTML,例如 weex、小程序等等,它们加载的页面并非 HTML,而且咱们要知足标准化场景的接入,因此咱们须要在 ESI 的基础上进行改造-TESI(Taobao Edge Side Includes),合适的才是最好的
基本的代码形式如何,咱们以数据预加载为例,以下 H5 中出现 TESI 标签(鼠标选中部分)
TESI 标签描述了一个 http 接口的信息,而且配置了其缓存时长 s-maxage,ER 会解析这个标签,而且在 ER 上发起请求,并将请求的数据按照 s-maxage 配置的值进行缓存,这就意味着下一次请求到相同的节点,就会直接返回缓存结果
渲染结果以下:
咱们看其实像数据预加载这种状况,在 HTML 中会渲染成一个 script 标签,其中存储的是一个全局变量方便运行时获取。
其实 TESI 标签不只能够用于 HTML 中,JS 中能够出现 TESI 标签,以下:
渲染后
其基本渲染原理以下,比较简单,这里不作赘余:
还有其余几种类型的标签以下:
标签名 |
描述 |
tesi:data |
数据预加载 |
tesi:esr |
边缘渲染 |
tesi:ssr |
SSR 静态化 |
tesi:redirect |
逻辑跳转 |
tesi:include |
区块引入 |
整个 ER 执行的过程会遇到各类各样的问题,甚至 ER 都有挂掉的风险,因此须要有稳定降级的预案保证不影响用户,因此咱们会将 CDN 源站指向页面 CDN 的源站,这样,及时 ER 解析出现问题,能够把解析前的页面直接返回给浏览器
ER 提供了两种缓存:内存缓存(如下简称 Cache)和 Swift KV 缓存(如下简称 KV),这两种模式在存取速度、体积大小、QPS 上都有差异,总结基本以下:
指标 |
VS | 胜出 |
存储空间 |
Cache ﹤ KV |
KV,可达 几十 GB |
QPS |
Cache ﹥ KV |
Cache |
存取速度 |
Cache ﹥ KV |
Cache |
存储反作用 |
Cache ﹥ KV |
KV |
这里指的存储反作用是指,存储大小对于 ER 性能的影响,存储在缓存中,若是存储体积接近内存大小,首先会影响 ER 执行性能,严重会致使 ER 容器重启
综合以上两种对比结果来看各有千秋,但合适的场景用合适的模式才是最好,为此咱们设计了二级缓存模式,一级缓存存入内存,二级缓存存入 KV,主要完成以下三个重点逻辑:
缓存的内容须要具有快速清除的能力,由于数据会更新、页面 bundle 会更新,特别是遇到紧急状况,例如线上问题紧急修复,须要可以实现缓存及时清除,因此须要必定的策略来知足需求,整体清除的逻辑会依赖请求,根据标签的身份信息进行清除
为了知足系统稳定性和安全生产的要求,TESI 标签的生产过程是须要被管控起来的,因此咱们要提供一个 TESI 的运维系统主要知足如下几个需求:
运维系统使用过程以下:
运维系统主要为了生成一个可用的 TESI 标签,真正发布生效咱们会借助于 DEF 发布系统,这样既沿用了标准,安全生产相关能力咱们也不用重复建设了,基本流程以下:
其基本的配置信息和执行过程以下,你们能够参考下:
仍是亘古不变的话题,若是你对边缘计算感兴趣,欢迎加入咱们共同建设,若是你对咱们的业务感兴趣,也欢迎你的加入,业务了解:zhuanlan.zhihu.com/p/128956855
简历投递:yabing.zyb@alibaba-inc.com
主题备注:前端-公司-名字