# 缩短资源传输距离
缩短资源的传输距离,最重要的是把资源存储在距离⽤户更近的地⽅。缓存,就是⼀种最常见的⽅案。在 WEB 系统中,缓存在不同环境
都有相关的实现。浏览器侧、APP 侧、CDN 侧,甚⾄后端服务器上,都可以做缓存。这⾥主要讨论服务端之外的缓存。缓存能很⼤地提
升系统性能,但相应地带来了额外的存储开销。同时,如何更新缓存,也是⼀个重要 的问题。
常见方案
-
配置 HTTP 缓存
-
原理:让资源从浏览器的 http 缓存中就近获取。HTTP 缓存是 Http 协议的标准功能,所有浏览器都会遵循。
-
适用资源:静态资源
-
实现方式:给资源添加 cache-control 响应 header,指定 max-age。推荐的⽅式是给静态资源设置长效缓存(设置⼀个极长的
本地缓存时间)+ ⾮覆盖式更新(每次发布会改变资源的 url 以使缓存失效 ) 的⽅式。以最⼤限度利⽤本地缓存的效果
-
-
使用 app 端缓存能力
- 原理: 让资源从⽤户端的离线缓存中就近获取
- 适用资源:app 中的页⾯使⽤到的静态资源
- 实现方式:使⽤端侧提供的离线包能⼒。离线包的缓存相对⽐普通 http 缓存更稳定,不容易被系统回收。(注意离线包更新问题)
-
使用 CDN 缓存
-
原理:让资源从 cdn 节点中就近获取
-
适用资源:静态资源
-
实现方式: 将静态资源上传⾄ cdn 源站,通过 cdn 节点和对应的域名来访问静态资源,并配置
cdn 对资源的缓存时间。(注意覆盖式更新问题)
-
-
使 ⽤ ServiceWorker + Cache
-
原理:Service Worker 可以在浏览器侧实现所有请求的⽹络拦截与响应,并结合 Cache Api ,以将任何 http 请求进⾏缓存。利⽤ ServiceWorker 的缓存还可以实现页⾯可 离线访问 的效果。即⽹络断开情况下,所能返回缓存中的内容。相⽐于 Http
Cache,Service Worker + Cache ,能更灵活地控制各个 http 请求在浏览器上的缓存 策略。
-
适用资源:所有
-
实现方式:PWA
-
# 减小资源开始加载时间
要减少资源开始加载的时间,主要是通过各种预加载(即提前加载)的手段。不管是静态资源,还是动态的数据接口,都有相应的方案,可以把请求提前。预加载的方案,要用到关键资源(即影响页面核心内容和功能的资源)上。核心资源一般是即将访问的页面的主 js、css,或者页面的主数据接口。
常见方案
-
静态资源预加载
-
原理:让浏览器提前发起页面面后续会用到的静态资源请求
-
适用资源:静态资源
-
实现方式:通过 html 的 prefetch link 标签
通过 html 的 preload link 标签
-
实现参考:可控静态资源预加载(后续文章会详细说明这种方案)
-
-
动态资源预加载
-
原理:让浏览器提前发起页面核心的动态资源请求。(注意接口需要的参数、登陆态,在请求时机提前后是否仍然可以获取到)
-
适用资源:动态资源(数据接口)
-
实现方式:
窗口侧提供预加载能力,在 webview 加载时就发起接口请求,并通过 bridge 将请求结果提供给页面的 js 消费。
在 html 尽量靠前的位置,发起页面主接口请求,可以通过全局 promise 变量的形式给页面后续的功能消费。
-
实现参考:
通过将数据请求,前置到主 js 前,可以让 js 的加载和执行,与接口请求并行起来,从而提升页面性能
接口提前请求
1
2
3
4
5
6
7
8
9
10
11<html>
<body>
<script>
// 在html尽量靠前部分发起数据请求
// 如果接口需要参数或者登陆状态,需要根据实际情况去获取
// 如果预加载多个接口,需要为不同接口设置不同的标识,以提供给后续消费
window._preload_promise_ = fetch('/data.json');
</script>
<script src="main.js"></script>
</body>
</html>消费提前请求的接口
1
2
3
4
5
6// main.js
// 要请求接口时,如果有预加载请求promise,就直接使用。否则发起数据请求
const dataResult = window._preload_promise_ ? window._preload_promise_ : fetch('/data.json');
dataResult.then(data => {
// 数据处理与展示
})
-
# 减少资源传输体积
随着浏览器能力的增强,业务功能的丰富,以及各种前端框架的发展,一个 WEB 页面加载的资源提及逐渐膨胀。减少资源的体积,除了可以优化页面的性能之外,对于移动端的页面,还能节省用户的流量。
各种资源都有体积优化的空间。前端工程师,通常关注的是编译后的 bundle 的体积,通过构建优化和各种压缩技术,减少构建后的 js、css 体积。除此之外,图片的体积、接口数据体积,也影响着页面的性能。针对不同资源类型,有着不同的体积优化方案。
常见方案
-
使用合适的方式压缩
-
原理:通过压缩算法减少资源传输体积
-
适用资源:所有资源
-
实现方式:
文本类型资源:gzip、br
图片类型资源:webp(对浏览器兼容性有要求)
-
实现参考:
-
-
构建优化静态资源体积(webpack)
- 原理:通过构建工具优化静态资源体积
- 适用资源:静态资源
- 实现方式:
-
精简动态资源内容
- 原理:减少动态内容中不必要的内容或字段
- 适用资源:动态资源
- 实现方式:
# 加快网络传输速度
⽹络速度,或许是性能环节中最不可控的⼀环。⽤户与服务器之间的⽹络速度,我们很难测量和⼲涉。但考虑到 http 的传输,不仅是物理链路的数据传输,还有各种⽹络协议参与其中。⽐如 dns、tcp、https 等。我们可以从协议侧⼊⼿,寻找有哪些加快 http 传输的⽅案。
另外,cdn 也是⼀个加速⽹络的途径。虽然 cdn 通常⽤于缓存静态资源,但也可以对动态资源进⾏⽹络加速(不缓存)。
常见方案
- 减少 DNS 传输时间
- 原理:减少 http 请求时,必须要执⾏的 dns 解析时间
- 适用资源:所有
- 实现方式:
- 实现参考:域名预解析、预建联、连接复用
- 减少 TCP/https 建连时间
- 原理:http 是基于 tcp 协议的,在⾸次建⽴连接时⽐较耗费时间,再加上 https 还要进⾏ ssl 握⼿,整个建连时间会更长,减少 tcp/https 的建连时间能有效提升⽹络传输速度,尤其对于⾸次访问。
- 适用资源:所有
- 实现方式:
- 使⽤ cdn 动态加速(全站加速)
- 原理:cdn 动态加速,可利⽤ cdn 节点和它们之间的⽹络,加快⽤户和中⼼化服务之间的传输速度
- 适用资源:动态资源
- 实现方式:使⽤ cdn 服务商的动态加速功能(付费的)
- 减少不必要资源传输
- 原理:多个资源同时请求,会占⽤⽹络带宽,影响页⾯的核⼼资源下载速度,在页⾯加载过程中,优先保证页⾯呈现所需要的核⼼资源下载,不让其他暂时⽤不到的资源参与⽹络竞争
- 适用资源:所有资源
- 实现方式:
- 使⽤ http2
- 原理:http2 相⽐于 http1 ,⽀持多路复⽤,且做了头部压缩,能从协议层加快传输速度
- 适用资源:所有
- 实现方式:cdn 运营商会⾃带,⾃⼰的服务,⼀般⽹关层也会⾃带。如果⾃⼰的服务没有开启 http2 ,或以联系运维同学资源如何开启
# 加快后端响应速度
后端是产出动态数据的关键方,对于需要实时展示最新内容的页面,性能的上限由后端决定。在后端也有很多种性能优化方案。这里仅列举一些常用的优化方案
常见方案
- 服务调用从串行改成并行
- 合并相似业务的接口,减少 http 请求
- 慢 SQL 优化,统计和分析 SQL 耗时,优化 SQL 性能
- 使用数据缓存,通过 memcache 或者 tair 等内存缓存,缓存查询结果,提升数据库查询性能
# 减少阻塞呈现时间
页面在渲染过程中,由于浏览器的机制,或者我们代码的逻辑,有些资源的加载和执行会影响后续其他资源。因此我们要尽量减少核心资源的阻塞点
常见方案
-
减少不必要的阻塞资源
-
原理:浏览器解析和渲染 html 过程中,会受 js,css 等资源的阻塞
-
适用资源:静态资源
-
实现方式:
-
实现参考:
我们在做项目时,有时会遇到环境变量文件
env_config.js
,这个文件作为关键配置要放在主 js 前,单独作为一个 js 在 html 里引入,在本地没有可用缓存时,会阻塞主 js 的加载和执行,可以直接在构建时,将文件的内容注入到 html 中,消除这个环境配置文件的加载对页面造成的阻塞1
2
3
4
5
6
7
8
9
10// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.plugin('html').tap(args => {
// 注入env变量至html
args[0].envVariable = fs.readFileSync(`env/env_${process.env.APP_ENV}.js`, 'utf8')
return args
})
}
}在 html 中使用此变量
1
2
3
4
5
6
7
8
9<html>
<body>
<script>
// 使用构建过程中注入的环境变量内容
<%= htmlWebpackPlugin.options.envVariable %>
</script>
<script src="main.js"></script>
</body>
</html>
-
-
服务端渲染(SSR)
- 原理:很多页面采用浏览器渲染的模式(CSR),这种模式会使页面的呈现受 html 加载、js 加载和执行、接口加载及页面渲染几个步骤的阻塞。通过服务端渲染,直接将页面核心内容在 html 请求中一起返回,减少阻塞首屏的依赖
- 适用资源:动态资源
- 实现方式:
# 提前呈现部分内容
页⾯要完整地加载完所有内容,通常⽐较耗时。如果能提前呈现部分内容,可以减少⽩屏时间,减少⽤户等待焦虑
常见方案
-
提前展⽰⾻骼图
- 原理:先给⽤户看⼀下⾻骼图⽽不是⽩屏,减少⽩屏时间和⽤户等待焦虑
- 适用资源:所有页面
- 实现方式:给页⾯设计⼀个与页⾯内容结构相似的⾻骼图,在页⾯完成内容回来前,优先展⽰⾻骼图。⾻骼图最好是不要依赖主 js 加载,只依赖 html 和及内联的少量 js , css
-
使⽤流式加载(bigpipe)
-
原理: 在⼀次响应⾥,利⽤ http 的流式特性,分段返回页⾯不同模块的内容,使内容较多的页
⾯,能先出来某些模块内容
-
适用资源:通过 ssr 输出的动态内容
-
实现方式:facebook bigpipe ⽅案
-
-
提前展⽰本地快照
-
原理:先给⽤户看⼀下之前看过的页⾯快照,减少⽩屏时间和⽤户等待焦虑
-
适用资源:对实时性要求不⾼的动态资源
-
实现方式:将接⼝内容甚⾄⾸屏 html ⽚断缓存⾄⽤户本地缓存(例如 localstorage),⽤户⼆次访问时,直接先读取本地缓存中的页⾯主体内容呈现给⽤户,然后再请求最新的数据替换掉快照内容(注意控制页⾯不要有明显闪动)
-
实现参考:
-
接口快照:缓存上一次接口
-
html 片段快照:缓存上一次渲染好的 html 片段
-
快照缓存机制
缓存位置:localStorage
缓存有效期:1 天
-
-