摘要:端缓存&端快照的方案是基于客户端容器所做的一套解决页面白屏缓存优化方案,ER缓存旨在边缘节点做的一套首chunk的缓存优化方案,减少html的回源耗时。
端缓存&端快照的方案是基于客户端容器所做的一套解决页面白屏缓存优化方案,ER缓存旨在边缘节点做的一套首chunk的缓存优化方案,减少html的回源耗时。
那么具体点,什么是端缓存&端快照,以及什么是ER缓存呢?这里将做一些基本的概念说明。所谓端缓存,顾名思义,就是客户端缓存,缓存的内容五花八门,可以是默认的首chunk缓存,也能是经过逻辑加工的html片段缓存。端快照就是经过代码逻辑处理存入客户端缓存的有真实内容渲染的html片段。ER缓存,用一句话概述就是在CDN边缘侧缓存初始化好的首chunk,其支持按发布静态资源版本、客户端类型、Query(如 itemId) 等作为缓存 key,能有限的支持千人多面。
上面的方案都是好方案,甚至达到开箱即用的程度。然而,在准备接入时却发现,与会员业务的技术方案存在极大的矛盾与挑战。
会员技术架构
这里简单的介绍一下我们会员页面的技术方案:页面路由模式。页面路由模式包含3层概念:page,module,render。SSR渲染的页面服务中,会最先打到我们的网关,由网关来确定当前用户会分发到哪一张页面,即page路由服务。当确定了是到哪一张页面后,就需要下发当下用户命中页面的所有静态资源和模块信息。当前页面用户命中了哪些模块,这就是module执行逻辑。最后就是模块的流式渲染。每个模块通过render进行独立渲染,并流式返回。
参考下图能较好理解:
一句话表达:即一个统一的url背后对应的许多张web页面。
路由服务我们知道html返回的时候,我们需要依赖页面的一些静态资源,比如js、css等。那么页面没过服务之前,是不知道自己需要加载什么资源的,例如一个链接:https://xxx.taobao.com/base/router,这个页面请求出去,A用户需要返回A页面,B用户返回B页面,C用户返回C页面。html里组装的静态资源也必须分别是xxx/a.js,xxx/b.js,xxx/c.js,css同理。
模块搭建在node服务器这一层,我们会在首chunk里注入当下用户对应页面所有搭建的模块信息,不同页面之间模块不同,配置的资源也不同,所以整体对应的搭建的模块对象值是不一样的。
ER缓存的使用最早期提供的首chunk维度的ER缓存,按照上述所讲的原因,页面静态资源&模块信息都会被缓存在cdn边缘侧,这会导致A用户本该打开A页面,结果现在边缘侧缓存的是B页面的资源,那么A用户就打开了B用户的页面。类似于一个400分的人,打开了本属于1000分用户的页面(例如当下缓存的是1000分用户的资源)。
我们想过不同的方案去区分不同页面,但是最后都无极而返。因为用户的状态存在实时的跃迁,比如淘宝会员过期,变成淘宝会员这种用户状态的变化,又或者不同AB实验人群的用户临时修改实验桶。为此,我们只能放弃ER首chunk的缓存方案,即在我们的业务场景下,是不能做任何相关的缓存的。
整个首chunk的缓存方案走不通,故而基于head缓存方案横空出世,逻辑用一句话解释:用户相关的动态资源不做缓存,所有页面共享静态资源做缓存。
我相信有人会问,如果首chunk没缓存下来,只缓存了head,那么整个首chunk的最终组装不还是要到node服务器回源吗?其实这个问题是对的。没毛病!绕不过回源。只有回源才能确定当前请求用户最真实的状态,明确他归属到哪张页面。那head缓存还有啥用?
这里的优势我分两块去讲:
(一):head的先返回,客户端侧可以先解析与执行。比如全局的js资源,全局的css,外部引入依赖的一些外联js以及内部通用的内敛js逻辑
(二):报文缩减,相较于之前的整个首chunk报文返回&传输,我们只有剩余的报文返回,那么自然而然会返回的更快一些。
当然,我们并不是所有的页面都是要过路由的,所有需要明确的页面,我们这边也是尝试开启首chunk缓存的,默认缓存10分钟。所以,技术方案终究都是积木块,到底要在什么页面选择什么技术方案,还是业务侧自己要去做组装决定的。当然,我们还有一些非路由场景,但是页面url参数的不同,会渲染不同的页面模板。这种也有现成的方案:通过query作为唯一key在边缘侧做缓存。
端缓存&端快照端缓存的概念我在这里就不赘述了,上面也已经提到过。我们的首chunk里有许多动态的东西,是跟随着用户真实状态走的。所以端缓存&端快照实现上,也存在一些蛋疼点,不同于ER缓存所面临的不同用户访问非预期页面的问题,端缓存&端快照所面临的是用户本身状态跃迁的问题。
按照端缓存的设计,fcc标之前的内容都会被缓存到本地,哪怕用户状态变化了,容器侧渲染也只取fcc标之前的文档片段(除非缓存失效或者head中的识别头发生变化)。故导致哪怕回源了,fcc标之前的内容依旧像个狗皮膏药一样黏在本地,无法撼动,一直跟随url资源请求出现在html中。
介于fcc标之前缓存在本地的问题,那么我们首chunk要进行一个设计,所有动态资源,一律下移,不保存在fcc标之前,这里就遇到一个很难受的点,就是这里的设计会和head的ER缓存存在一些冲突,导致我们能在head的ER缓存中可存的内容更少了。
从图上不难看出,所有和用户相关明确的动态的一些东西,我们全部做了后移,来保障不受到缓存的影响。
由于样式表是动态的,跟随页面而动的,所有样式表的加载也是在fcc标之后进行的导入,这就导致一个问题,快照的样式如何处理?为此,我们只好将样式进行动态请求,并结合真实的dom元素一起进行拼装,塞入到快照节点中。这样就能保证快照渲染的dom样式一定是存在且是全的。
端快照是按照url的前置维度做的存储,那么我们这边触发的用户状态跃迁,就会导致页面内容闪屏(全闪!!),即用户上一刻的状态属于非淘宝会员,但是下一刻经历了开卡,则用户状态跃迁成了淘宝会员,非淘宝会员和淘宝会员是完全两张不同的页面,快照上一秒存的还是非淘宝会员的内容 ,如果不做处理,这里就会导致快照从老的页面闪成新的页面,这样的体验和体感都不好,为此专门找容器同学提了需求,容器支持跨页面清除端缓存&端快照,并提供出api供使用。
我们都知道,快照是记录的上一次用户的UI渲染,那么记录之后,用户在页面上做了一些操作,例如领取了红包,那么这里就存在舆情风险。比如某张券缓存下来了,是多少的面额以及未领取状态,然后用户领取了,面额变成了一张小的未领取。用户第二天进入显示的快照中还是大面额的券快照,然后券消失了(真实场景券已领完),那么用户就会投诉,说xxx券没掉了。会员核心的都是权益,权益存快照,就有时效性的问题。为此,我们专门设计了一套在快照层渲染的UI,即快照骨架。
如下图所示,左图为快照,右图为真实内容,从左图切成右图,我们发现,在快照的视图上,我们定制了一套替换内容,同时模块在真实页面还可以正常渲染,来保障快照UI渲染的安全性。
当然,除了骨架的UI渲染方式外,我们还支持模块可以不在骨架中展示,来保障整个模块的安全性。
页面中快照的存在,对于线上用户的正常渲染我们很难监控。退一万步讲,如果快照渲染有问题,我们要如何快速止损呢?为此我们结合模块搭建平台实现了快速配置快照开关的能力。通过这个开关,能做到快速下线快照&清除用户当前手机老的端缓存!减少对用户造成进一步的伤害!
为了保障快照能最大程度的给用户带来性能体验优化,我们把快照的缓存时间做成了动态配置,不同的页面有不同使命,那么其生命周期也有所不同。我们结合了页面的回访率,来给用户做一个数据参考,为此我们专门洗了页面报表,产出次日回访、三天后回访、7天回访、10天回访等多个指标,根据淘宝会员在不同月份的特性,可以有足够的数据支持来动态设置端缓存过期时间。
结语
所有提供开箱即用的方案,都可能和自己业务特性存在出入,有的需要特殊处理,有的则需要专门定制,我们还考虑了从手机淘宝出发,结合淘宝首页、我淘、商详等大流量口径来实现ER缓存的“精准命中”。同样的,端快照,我们梳理清楚他的核心作用是解决白屏问题。但是解决了白屏问题,那么对业务或者体验上是否有副作用,这个也需要额外关注,不能说解决一个问题,却引入好几个其他问题。
快照我们依旧还存在一些体验问题,比如feeds加载后的内容变更,图片闪变,渐进式的加载效果也不理想,我们计划后续对于突兀变化的内容,全部通过在快照这层视图上做动效,用最柔和的方案来解决突变、闪变等一些列体验差的问题。
团队介绍
我们是淘天集团-技术线-会员技术团队,团队中既有88VIP、淘宝省钱卡、天猫积分等淘天集团的重点业务,也有提供亿级QPS为全淘宝业务提供底座支持的淘宝账号与会话、淘宝收货地址等基础产品平台。我们肩负着会员增长和大盘成交的使命,以数智化的手段和多种增长策略撬动会员业务增长。在文化上我们倡导追求卓越和责任担当,在技术上我们坚持技术创新与突破。在这里你可以了解到会员基础交易体系、权益履约体系、积分营销玩法体系。一起做大规模、服务好电商高价值用户。
来源:霸气的科技国王