前端 SSR 在之家主站的应用-缓存及其性能监测
关注“之家技术”,获取更多技术干货
总篇150篇 2022年第25篇
★ 目录 ★
01 |
前言 |
02 |
基础概念 |
03 |
接口层的缓存方案3.1 细化场景 3.2 实现路径 |
04 |
请求耗时直观化4.1 直观的ServerTiming 4.2 实现路径 |
05 |
结语 |
一、前言
汽车之家用户产品中心的前端团队,将 SSR 同构技术应用在 PC & M 主站中。相对于原有技术方案,在页面渲染性能、白屏时间、可维护性、用户体验都有大幅度的提升。我们结合公司技术基础设施和自身特点,经过长时间的升级与优化,逐渐沉淀出了一套最佳实践。本文即介绍其中的两个技术环节,以飨读者,分别是:
-
接口层的缓存技术方案- 如何在应用中使用缓存优化接口性能
-
请求耗时可视化的实现- 如何使用浏览器方便的看到接口请求耗时
二、基础概念
随着前端复杂度的不断升高,浏览器能力被无限放大和利用,伴随着 NodeJS 的强势崛起,各种应用框架的层出不穷,从 CSR 到 SSR,从 SSR 再到同构。放眼当下,在不同的业务场景中,充斥着它们不同的身影。本次分享的开篇,我们先从以下几种常见的渲染模式讲起:
-
SPA :单页面应用(Single Page Application)。动态重写当前的页面来与用户交互,而不需要重新加载整个页面。单页应用做到了前后端分离,后端只负责处理数据提供接口,页面逻辑和页面渲染都交给了前端。
-
CSR :客户端渲染 (Client Side Render)。顾名思义,渲染过程全部交给浏览器进行处理,服务器不参与任何渲染。页面初始加载的 HTML 文档中无内容,需要下载执行 JS 文件,由浏览器动态生成页面,并通过 JS 进行页面交互事件与状态管理。
-
SSR :服务端渲染 (Server Side Render)。DOM 树在服务端拼接生成完整的 HTML 之后再返回,即当前页面的内容是服务器生成好一次性地给到浏览器进行渲染。
-
Prerender :预渲染,常见的有 SSG 等,在打包的阶段就预先渲染页面,所以在请求到 index.html 时就已经是渲染过的内容。
-
SSR 同构 :客户端渲染和服务器端渲染的结合,在服务器端执行一次,用于实现服务器端渲染(首屏直出),在客户端再执行一次,用于接管页面交互(绑定事件),核心解决 SEO 和首屏渲染慢的问题;后续页面交互可复用已加载的静态资源,最大程度地平衡用户体验和性能。
在 SSR 同构应用架构中,我们将缓存技术应用在了诸多技术环节,其中包括: CDN 层 、 Nginx 反向代理层 、 SSR 渲染服务层 、 接口 API 缓存层 等,通过它们,共同实现了 SSR 性能提升质的飞跃。
三、接口层的缓存方案
本篇文章,我们着重将目光聚焦于源站中的一环 - 接口 API 缓存层 (上图中红色图标部分),在整个缓存链路中,API 接口缓存也极为重要: 因为它可以保障接口查询耗时稳定高效,不因程序复杂或数据庞大而产生耗时波动;同时,它还可以在源接口发生故障时,通过返回历史数据的方式提供容灾能力,减少由此带来的影响,从而提升终端用户使用体验。
3.1 细化场景
通过实际的调研分析,我们整理了一些缓存的应用场景,并对其进行了一一实现:
-
可实现一键全部开启缓存,及单接口个性化配置,可覆盖全局配置或开关缓存
-
可实现在不同宿主环境下选择不同介质进行存储:Redis、Node Cache、浏览器内存等
-
默认对 GET 请求类型生效,当然,也可以通过高级配置参数支持更多
-
当源接口响应失败,可选择性开启容灾功能
-
自动同步源接口响应头中
cache-control
字段,作为缓存有效期
除了以上的一些核心功能,对于基础的 TTL 有效期、缓存 Key 生成规则、日志输出等,均已内置支持。
3.2 实现路径
Axios 是一个非常优秀而又被广泛使用的请求库,我们选择其的原因还在于它天然对 Node 端和浏览器端的双重支持。
那么,它是如何实现对 Node 端和浏览器端的双重支持的呢?那就是,内置适配器 - adapter;
我们先通过一张图来了解适配器在 Axios 中扮演的角色:
从图上可以看出,若想实现对接口请求的拦截和响应,大致有两种方式:
-
通过拦截器处理,在请求拦截器和响应拦截器分别添加缓存相关逻辑代码
-
实现自定义适配器,内部代理原适配器,执行缓存相关逻辑,并进行请求及响应处理
在实际业务场景中,拦截器往往是用于处理业务相关的通用数据逻辑,若想由此实现该功能,需要在请求拦截器和响应拦截器中,分别加入相关代码,对业务逻辑有一定的侵入性,集成方式繁琐,不够灵活,我们更期望的是一种零侵入、插拔式的集成方案。
因此,我们实现了一个自定义适配器, 在不改变现有业务代码前提下,通过一行代码即可实现开启 / 关闭全应用的数据缓存。
如果你对适配器还不甚了解,可以先移步官方文档进行进一步了解。下面是一个自定义适配器的最简单的示例:
module.exports = function coustomAdapter(config) { return new Promise(function dispatchRequest(resolve, reject) { // ...other code } }
为了方便集成到项目中,我们发布并提供了一个 Npm 包,并向外暴露出了配置器;除此之外,结合之家的接口数据规范,还对 Axios 进行了业务上的封装,可以更方便的应用到日常的业务开发中。
import { installCache, installRequest } from '@ace/request';
const { adapter } = installCache({
ttl: 3 * 1000,
// ...other cache 参数
});
// 通过以下方式,可以单独将适配器集成到已有项目中
const reqIns = axios.create({
baseURL: 'http://api.autohome.com.cn',
// 自定义 adapter
adapter,
// other options
});
// ...other code
四、请求耗时直观化
4.1 直观的 ServerTiming
以往的方式,如果我们想了解接口性能,只能在应用中打日志来记录相关指标数据,而该数据的获取相对繁琐且滞后。为了解决这个问题,可以用浏览器直观快速的获取,我们引入了 ServerTiming ( 参考链接见文末 ) 。
当 adapter 被集成使用后,为了能够实时分析 SSR 中接口请求的耗时情况,为此,我们开发了一个工具 - ServerTiming-Loader。
顾名思义,它是一个 Webpack Loader,通过它,便可以在不侵入业务代码的前提下,由构建层前置将耗时统计代码注入到业务代码中,为页面请求追加 ServerTiming 响应头,并在浏览器的开发者面板中实时体现出来。
如下图所示,以某次页面加载为例,页面首屏接口耗时为 11ms,使用这个时间指标,往往在 FCP、LCP、TTFB 等性能分析时,非常具有参考意义。
4.2 实现路径
得益于 Next.js 框架本身的一些强约束,在 Webpack 编译过程中,我们可以很方便而又准确的定位到哪些模块属于页面级源文件,同时在页面源文件中能够通过静态分析得出
getServerSideProps
方法代码段。参考 Babel 的核心三步的工作流程(如下图),我们修改了其中的内部实现:通过 Babel 对 AST 解析及转换的能力,将
getServerSideProps
包装为高阶函数,并在函数内部追加上耗时统计代码。
Babel 工作流程:Parse(解析源文件)-> Transfrom(转换)-> Generator(生成新文件)
最终,我们将会生成如下模板代码:
/** * 定义新的 getServerSideProps,并调用 API 执行 */ const getServerPropsTempl = astTempl( ` /** * 追加 getServerSideProps 耗时到 Chrome Tools / Timing */ const getPropsWithTimingLoader = async ( cxt ) => { const startTime = Date.now(); const returnProps = await originalGetServerSideProps(cxt); const { res } = cxt; setServerTimingHeader(res, { key: 'API', value: \`dur=\${Date.now() - startTime}\`, }); return returnProps; }; export const getServerSideProps = getPropsWithTimingLoader; `, { placeholderPattern: false } );
在 AST 的遍历过程中,我们还需要识别出 getServerSideProps 的不同声明形式,比如:
export const getServerSideProps = async () => { // ...code }
或
export async function getServerSideProps (){ // ...code }
对于无法匹配或者匹配失败的语法类型,将原代码进行返回,保证页面功能的正常执行。
astTraverse(pageAST, { /** * 去除掉 getServerSideProps 的模块导出 * 示例: * -------------------------------------------------------------------------------------- * 转换前:export const getServerSideProps = async () => { ... } * 转换后:const getServerSideProps = async () => { ... } * --------------------------------------------------------------------------------------- * 转换前:export async function getServerSideProps (){ ... } * 转换后:async function getServerSideProps (){ ... } */ ExportNamedDeclaration(path) { // ...codes }, /** * 将原 getServerSideProps 方法重命名为 originalGetServerSideProps * 当前为字面量形式声明 */ VariableDeclarator(path) { // ...codes }, /** * 将原 getServerSideProps 方法重命名为 originalGetServerSideProps * 当前为 function name 形式 */ Identifier(path) { // ...codes }, });
五、结语
在 SSR 项目中,接口请求可能出现在 Node 和 浏览器等不同的宿主环境中,借助 Axios 请求库的一些特性,我们对其进行了一些封装,通过不同的缓存介质,实现了请求劫持、数据缓存和精细化场景的缓存配置,在 Server 端,通过 Redis 还可以实现多实例的缓存数据共享;同时,通过在构建层的一系列预处理操作,在 SSR 应用的运行时,通过 Server Timing 还可实现追踪页面 Server 端实时的请求耗时,并体现到浏览器的开发者面板中。
至此,API 接口缓存技术层在 SSR (请求)中的应用,我们已经介绍完了,在日常的开发中,我们可以视业务场景进行选择性开启,针对 CSR / SSR 不同项目类型上的调用方式也并无差异;当然,以上内容只是 SSR 中应用缓存链路的某一个环节,在 Render 层与更前置的 HTTP Server 层,我们有着更丰富的缓存技术方案在应用,期待下一次的分享。
参考文档
-
axios(http://github.com/axios/axios)
-
next.js(http://nextjs.org/)
-
Server-Timing(http://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Server-Timing)
-
Babel(http://babeljs.io/)
-
Webpack(http://webpack.js.org/)
-
npm docs(http://docs.npmjs.com/cli/v8/configuring-npm/package-json)
作者简介
汽车之家
米梦宇
用户产品中心-App技术部
2019 年加入汽车之家,目前任职于用户产品中心-App技术部-前端开发团队-产品库前端开发组。
主要参与汽车之家产品库相关前端开发工作。
阅读更多:
▼ 关注「 之家技术 」,获取更多技术干货 ▼
- 小米机器人相关技术专利超 740 件,未来 5 年研发总投入预计超 1000 亿元
- 联想再布局教育领域:将加大教育公益和技术研究投入
- 汽车之家APP基于Mach-O的探索与实践
- 全球最高酒店类建筑“迪拜蓝天酒店”顺利封顶:中国铁建 306 米高空浇筑混凝土,用上 BIM 技术
- 索尼为 Alpha 7 IV 相机推出机内照片防伪技术
- 阿米巴经营法
- 华为与阿联酋电信与合作完成首次 6GHz 5G 技术试验
- 工信部:进一步加快虚拟现实等技术在各行各业应用
- 浅谈Python requests pytest接口自动化测试框架的搭建
- 炬芯科技:LE Audio 技术取得阶段性成果,部分产品已量产
- VR 技术助力:英国和巴西医生完成复杂连体婴分离手术
- 被指正在失去技术优势,三星将积极提升芯片业务竞争力
- 联想拯救者 Y700 游戏平板“冰魄白”与“炫光蓝”配色亮相,采用新一代电致变色技术
- 作为插件全埋点解决方案
- 网络请求组件封装
- Flutter 绘制探索 | 箭头端点的设计
- 小米新专利可对虚拟角色进行基因繁殖:形象无法预期,并结合区块链技术
- TCL 华星参展国际显示技术大会,全球首款 17 英寸 IGZO IJP OLED 折叠屏亮相
- 业内人士:不只有娱乐、通信,无线充电技术未来可为电动汽车提供更多功能和用途
- 浅谈Python reqest pytest接口自动化测试框架的搭建