缓存机制
#缓存机制
缓存是性能优化中简单高效的一种优化方式,可以显著减少网络传输带来的损耗
我们发起一个网络请求,主要包含三个步骤:发起请求
、后端处理
、请求响应
我们可以在第一和第三步中优化性能,比如说直接使用缓存不发起请求
、发起请求,如果跟前端一致,不需返回数据
缓存位置
缓存在浏览器中主要存在于:
Service Worker
Memory Cache
Disk Cache
Push Cache
网络请求
有各自的优先级,当依次查找缓存都没有命中时,才会去请求网络
Service Worker
Service Worker
是运行在浏览器背后的独立线程
,一般可以用来实现缓存功能。它与其他的缓存机制不同,我们可以自由控制缓存那些文件,如何匹配缓存,如何读取缓存,并且缓存是持续性
的
传输协议必须为HTTPS
,因为涉及到请求的拦截,保障安全。
使用Service Worker
实现缓存功能的步骤:
- 先注册
Service Worker
- 监听
install
事件,就可以缓存需要的数据 - 下一次用户访问的时候可以通过
拦截请求
的方式,查询是否有缓存,有的话直接读取缓存,否则才去请求数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 注册Service Worker
if (navigator.serviceWorker) {
navigator.serviceWorker.register('serviceWorker.js')
.then((registration)=>{
console.log('serviceWorker 注册成功')
})
.catch((registration)=>{
console.log('serviceWorker 注册失败')
})
}
</script>
</body>
</html>
// serviceWorker.js
// 监听install事件
self.addEventListener('install', (e) => {
e.waitUntil(
// 打开缓存,缓存需要的数据
caches.open('my-cache').then((cache) => {
return cache.addAll(['./index.html'])
})
)
})
// 拦截所有请求事件
self.addEventListener('fetch', (e) => {
// 在 Cache 对象中查询第一个匹配URL请求。如果没有发现匹配项,该代码将转而从网络获取响应。
e.respondWith(
caches.match(e.request).then((response) => {
// 如果存在缓存,直接返回
if (response) {
return response;
}
// 否则请求资源
console.log('fetch source')
})
)
})
Memory Cache
内存中的缓存
- 高效
- 缓存持续性短,随着进程的释放而释放
- 当我们刷新页面时,很多数据都是来自内存缓存
- 对大文件来说,大概率不存储在内存中
- 如果当前系统的内存使用率高,文件会优先存储进硬盘
Disk Cache
存储在硬盘的缓存
- 读取速度慢
- 可以存储任何文件
- 容量大,存储时效性
- 在所有浏览器缓存中,占比是最大的
- 根据
HTTP Header
中的字段判断资源是否需要请求,资源是否过期 - 即使是跨站点的情况下,相同地址的资源一旦被硬盘缓存,也不会再次去请求数据
Push Cache
HTTP/2
中的内容,当以上三种缓存都没命中时,才会使用
让服务器能够在客户端请求一个资源的时候,预先下发另外一个资源。
- 缓存时间短暂,只在
session
中才生效,会话结束就会释放 - 所有资源都能被推送,
Edge
和Safari
兼容性不好 - 可以推送
no-cache
和no-store
的资源 - 一旦连接关闭,Push Cache就被释放
- 多个页面库使用相同的
HTTP/2
连接,也就是说能使用相同的缓存 Push Cache
中的缓存只能被使用一次- 浏览器可以拒绝接受已经存在的资源推送
- 可以给其他域名推送资源
参考链接
网络请求
如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。
缓存策略
缓存策略均通过设置HTTP Header
来实现的
强(制)缓存
强缓存表示缓存期间不需要请求,状态码为200
Expires
HTTP/1的产物。
表示到某个 HTTP 日期
后到期/过期,资源过期后则需要重新请求。
但受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
Cache-control
出现在HTTP/1.1,优先级高于Expires
。默认值为private
常用指令:
public
/private
:注意private
表示只有响应的最终接收方可以缓存,可以防止公共缓存(如cdn
)存储个人信息max-age=<seconds>
定义了一个相对于请求时间的有效期,单位为秒,在规定时间内,浏览器不需要重新回访服务器验证该文件- 如果超过时间,有新的文件供浏览器下载,服务器返回
200
,浏览器下载该文件,会删除旧文件为缓存,应用新的缓存报头 - 如果文件没有更新,那么服务器会返回
304
,不需要下载文件,用新的报文来替换旧的缓存
- 如果超过时间,有新的文件供浏览器下载,服务器返回
no-store
表示不会以任何方式缓存no-cache
表示每次都向服务端去验证资源的新鲜性,是一种兼顾文件新鲜度和获取缓存资源的方式- 如果有新资源,返回
200
和新的资源 - 如果没有,返回
304
,重用缓存中的版本
- 如果有新资源,返回
must-revalidate
需要和max-age
关联使用,当资源过期时,会立即重新访问服务器,如果没新资源,返回304
,重新计时,反之,返回200
,缓存更新。- 有宽期限的
no-cache
- 适用于博客场景
- 有宽期限的
协商缓存
如果缓存过期了,就需要发起请求验证资源是否更新,当资源没有发生改变,那么返回304
,并且更新浏览器缓存有效期。
Last-Modified和If-Modified-Since
Last-Modified
存在于response header
中,If-Modified-Since
存在于request header
中。
Last-Modified
表示本地文件最后修改日期,If-Modified-Since
会将Last-Modified
的值发送给服务器,询问该日期后是否有更新,有的话将新的资源发过来,没有的话返回304
。
但是存在一些弊端:
- 在本地打开缓存文件,即使内容没修改,
Last-Modified
也会被修改,服务器不能命中缓存导致发送相同的资源 - 以秒计时,如果在不可感知的时间内修改文件,那么服务端会以为资源还是命中了,不会返回正确的资源
ETag和If-None-Match
ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级比 Last-Modified 高
。
如果什么缓存策略都没设置,那么浏览器会采用一个启发式的算法,通常会取响应头中的date
减去Last-Modified
值的10%
作为缓存时间
实际场景应用
频繁变动的资源
使用no-cache
使浏览器每次都请求服务器,配合ETag
或者Last-Modified
来验证资源是否有效。这种方法无法减少请求次数,但能显著减少响应数据大小。
代码文件
特指HTML以外的代码文件,HTML一般不缓存或者缓存时间很短。
在打包的时候可以对文件名进行Hash
处理,只有代码被修改了,才会生成新的文件名。
基于此,我们可以给代码文件设置缓存有效期一年:Cache-Control:max-age=31536000
,这样只有当HTML文件中引用的文件名发生改变时,才会去下载新的代码文件,否则就一直使用缓存。