4.1-浏览器缓存机制
Create by fall on 22 Nov 2020 Recently revised in 10 Sep 2023
浏览器缓存
为什么很多站点第二次打开速度会很快?
显而易见,因为缓存(将图片,css等静态资源保存到本地),将一些静态资源存储到本地,就不会再向服务端进行请求,而是从本地进行调用。
介绍
缓存分为服务端侧(Server Side,比如 Nginx、Apache)和客户端侧(Client Side,比如 web browser) 常用的服务端缓存有 CDN 缓存,客户端缓存就是指浏览器缓存。
网页加载中,耗时最多,最不稳定的,就是从服务器获取资源。
为什么使用浏览器缓存?
- 请求更快:通过将内容缓存在本地浏览器或距离最近的缓存服务器(如CDN),在不影响网站交互的前提下可以大大加快网站加载速度。
- 节省带宽:对于已缓存的文件,可以减少请求带宽甚至无需请求网络。
- 降低服务器压力:在大量用户并发请求的情况下,服务器的性能受到限制,此时一些静态资源不会多次请求,可以起到均衡负载的作用,降低服务器的压力。
缓存的种类
- 数据库缓存:当Web应用关系复杂,数据表爆炸式增长时,可将查询后的数据放到内存中进行缓存,下次查询时,直接在内存中获取,以提高响应速度。Memory Cache
- CDN 缓存:当我们发送一个 Web 请求时,CDN 会帮我们计算器哪里得到这些内容的路径快且短
- 代理服务器缓存:跟浏览器缓存性质类似,但是代理服务器缓存面向对象更广,规模更大,他不止为一个用户服务,一般为大量用户提供服务,同一个副本会被重用多次,因此在减少响应时间和带宽方面
- 浏览器缓存,每个浏览器都实现了HTTP缓存,我们通过浏览器使用HTTP协议与服务器交互的时候
- 缓存过程:强制缓存,协商缓存
- 缓存位置:
Service Worker -> Memory Cache -> Disk Cache -> Push Cache
缓存机制
缓存机制包括:强制缓存,协商缓存,执行顺序如下
- 先判断强制缓存:浏览器发送请求前,根据请求头的
Expires
和Cache-Control
判断是否命中强制缓存策略(包括是否过期),如果命中,直接从缓存获取资源,不会再请求服务器。 - 再判断协商缓存:没有命中强制缓存规则,浏览器会发送请求,根据请求头的
Last-Modified
和Etag
判断是否命中协商缓存,如果命中,直接从缓存获取资源。 - 如果前两步都没有命中,则直接从服务器获取资源。
强制缓存 | 协商缓存 | |
---|---|---|
是否会请求服务器 | 不会向服务器发送请求 | 先请求一下服务器询问该缓存是否可用 |
读取内容位置 | 客户端本地 | 客户端本地 |
返回状态码 | 200 | 缓存可用则返回 304,不可用则重新请求 |
相关响应头 | Expires**、**Cache-Control | Last-Modified、If-Modified-Since、Etag、If-None-Match |
强制缓存
强制缓存有两个相关头字段
- 通过绝对时间判断:Expires,给一个时间点,时间点内都会命中强制缓存
- 通过相对时间判断:Cache-Control,比如两个小时,两个小时内,就会命中强制缓存
Expires
用来设置资源的过期时间的头字段,浏览器通过将其与当前本地时间对比,判断资源是否过期,在设置时间之后则过期。
例如:Expires: Sun, 20 Jun 2021 02:19:39 GMT
缺点:Expires 是服务器下发的绝对时间,无法保证客户端与服务器时间的一致性
Cache-Control
指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置 Cache-Control 并不会修改另一个消息处理过程中的缓存处理过程。
Cache-Control
它是通用消息头字段,被用在 http 请求和响应中,通过设置指定指令实现缓存机制。
Cache-Control
响应头包括以下字段:
最重要的指令是 max-age=<seconds>
,例如 Cache-Control: max-age=31536000
设置了一年后过期
max-age
以秒为单位,设置的是相对时间,表示距离发起请求的时间的秒数。无论本机与服务器时间是否一致,最后以相对时间为准;在此时间内不会再去请求服务器,直接去浏览器拿缓存。
其他取值为:
public
:指示响应可被任何缓存区缓存privite
:指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效no-cache
:请求或响应消息不能缓存,客户端有缓存资源,但是否缓存需要协商缓存进行验证。no-store
:用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。不使用任何缓存min-fresh
:指示客户机可以接收响应时间小于当前时间加上指定时间的响应max-stale
指示客户机可以接收超出超时期间的响应消息。如果指定max-stale
消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。no-transform
、must-revalidate
、proxy-revalidate
等等
Cache-Control: private, max-age=0, no-cache
完整的缓存指令 Cache-Control
请求头 Cache-Control
请求时的缓存指令包括 only-if-cached
Expires 和 Cache-Control 两者之间的关系:
- Expires:是
HTTP/1.0
协议中的 - Cache-Control:
HTTP/1.1
中新增,来定义缓存过期时间
在实际项目中会同时使用两个缓存策略
HTTP/1.0 还有一个功能比较弱的缓存控制机制:Pragma。
请求中包含 Pragma 的效果跟在头信息中定义 Cache-Control: no-cache 是相同的。使用 Pragma 时将忽略 Expires 和 Cache-Control 头,通常定义 Pragma 是为了向后兼容基于HTTP/1.0的客户端。
协商缓存相关头字段
协商缓存共有两种判断方式,有相关的四个缓存头
- 使用时间判断:Last-Modified 、If-Modified-Since
- 使用唯一标识符:Etag、If-None-Match
Last-Modified:浏览器第一次请求资源时,服务器返回资源的同时,会在响应头中添加 Last-Modified
字段,写明资源最后一次被修改的时间。
Last-Modified: Fri, 23 Oct 2020 10:49:15 GMT
浏览器获得了资源的最后一次修改时间
If-Modified-Since
再次进入该网页,浏览器再次发送请求时,会将上次的时间先写入If-Modified-Since
,询问服务器是否在该时间之后更新了内容,服务器接收后,会和Last-Modified
对比,如果Last-Modified
大于上一次修改时间(Last-Modified-Since
) 会显示已经修改,重新返回资源并显示 200,否则返回 304,使用浏览器的缓存。
浏览器将获得的时间传给服务器,进行对比。
缺点:
- 如果请求时间相同,一秒之中多次修改,就无法返回最新信息
- 只要资源改变(服务器动态生成),就会改变 Last-Modified,而实质内容不变,就起不到缓存的作用
Etag、If-None-Match
Etag: 是由服务端生成的(一般采用 hash 算法生成),只要资源有变化,Etag就会重新生成。服务器返回资源的同时,会在响应头中添加该字段,浏览器会将 Etag 与资源缓存。
If-None-Match:浏览器再次请求同一个资源时,会在请求头中将上次的 Etag
的值写入到请求头的 If-None-Match
字段中。 然后,服务器会将 If-None-Match
的值与服务器上该资源的 Etag
字段进行对比。如果一致,则表示未修改,响应 304 状态码;如果匹配不上,响应 200 状态码,并返回最新数据。
并且该优先值会高于 Last Modified
和 Last Modified Since
存储位置
缓存读取顺序
- 当有 Service Worker 时,浏览器会从 Service Worker 读取缓存
- 再去内存读取缓存,如果有,直接加载
- 如果内存没有,则从硬盘读取,如果有直接加载
- 如果硬盘也没有,那么就进行网络请求
- 获取到的资源缓存到硬盘和内存
不同的存储位置有各自的优先级,当查找的缓存都没有命中的时候,才会请求网络
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
Service Worker
Service Worker:运行在浏览器背后的独立线程,一般可以用来实现持续性缓存功能。
并且是实现 PWA(Progressive Web App) 的重要模块之一。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
Service Worker实现缓存涉及到请求拦截(拦截get,post请求),如果是http协议,容易被拦截,出现安全隐患所以传输协议必须为 HTTPS 协议,保障安全。
实现 Service Worker缓存一般分三步
- 首先需要先注册 Service Worker
- 然后监听到 install 事件以后就可以缓存需要的文件
- 用户下次访问就可以通过拦截请求的方式查询是否存在缓存,存在缓存就可以直接读取缓存文件,否则向服务器请求数据
Service Worker 没有命中缓存时,需要去调用 fetch 函数获取数据。因为没有命中缓存,会根据缓存查找优先级去查找数据。
但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
Memory Cache
Memory Cache 指内存中的缓存,主要包含当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。
读取内存中的数据比读取磁盘快,虽然高效,但是会随着进程的释放而释放,一旦关闭tab栏,缓存就会释放。
内存缓存在缓存资源时并不关心返回资源的 HTTP 缓存头 Cache-Control 是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对 Content-Type,CORS 等其他特征做校验。
一般存储JS,字体,图片等
Session 也会存储在内存中
Disk Cache
Disk Cache 存储在硬盘中的缓存,读取速度慢,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。
根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。
一般存储CSS等
Push Cache
Push Cache(推送缓存)其它三种缓存都没有命中时,才会被使用。是HTTP/2中的内容。
推送缓存只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,它并非严格执行 HTTP 头中的缓存指令。
-
推送:所有的资源都能被推送,并且能够被缓存。但是 Edge 和 Safari 浏览器支持较差。
-
Push Cache 中的缓存只能被使用一次
-
可以给其他域名推送资源
-
浏览器可以拒绝接受已经存在的资源推送
-
一旦连接被关闭,Push Cache 就被释放
-
可以推送 no-cache 和 no-store 的资源
-
多个页面可以使用同一个 HTTP/2 的连接,即使用同一个Push Cache。出于对性能的考虑,部分浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。主要依赖浏览器的实现而定。
内存中读取顺序
内存中读取顺序(访问缓存优先级)
- 先在内存中查找,如果有,直接加载
- 如果内存中不存在,则在硬盘中查找,如果有直接加载
- 如果硬盘中也没有,那么就进行网络请求
- 请求获取的资源缓存到硬盘和内存
缓存的设置
Local Storage
- 不设置过期时间可以实现永久存储
- 收到同源策略影响
- 最大可以存储 5M,因此被称作客户端的微型数据库,当然,现在很多浏览器都推出了存放大量数据的 indexDB
- 只能存储字符串
- 在隐私模式不可读
- 本质上是对字符串的读取,读取内容过多(读取操作是同步的)会导致页面变卡
IE8 以下不兼容 Local Storage
使用
// 对象声明方式
localStorage.setItem('a','1')
localStorage.b = '2'
localStorage["c"] = '3'
localStorage.removeItem('a') // 清除 a 的数据
localStorage.clear() // 清除全部缓存
测试 localStorage 的兼容性
function storageTest(){
const testKey = 'test_key';
const testValue = 'test_value';
let isSupport = false;
try {
localStorage.setItem(testKey, testValue);
if (localStorage.getItem(testKey) === testValue) {
isSupport = true;
}
localStorage.removeItem(testKey);
return isSupport;
} catch(e) {
if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
console.warn('localStorage 存储已达上限!')
} else {
console.warn('当前浏览器不支持 localStorage!');
}
return isSupport;
}
}
Cookie
Cookie 的详情可以在 3.2 章节查看
- 会话跟踪技术
- 存储重要的信息:购物车信息,视频是否点赞,网页是否浏览过,视频播放进度
- 需要设置过期时间
- cookie 的数据会自动的传递到服务器;
- 最大可以存储 4 KB
- 每一个域名下最多可以存储 50 条
- 只能存储字符串
- 格式:
name=value;[expires=date];[path=path];[domain=xxx.com];[secure]
document.cookie = 'username=taly'
console.log(document.cookie)
// 存储中文的时候需要进行编码存储
document.cookie = 'username='+ encodeURIComponent('苍树人老师')
console.log(decodeURIComponent(document.cookie))
// 可以将过期时间设置为负值,然后就可以删除 cookie
// 快速设置cookie过期时间
document.cookie = 'username=xxx;expries='+new Date(0)
- expires:过期时间,默认为会话,必须放置表示过期时间的 date 对象
- path:默认值为当前路径,获取cookie值时,必须和下载文件的路径必须一致
- domain:限制访问域名,如果加载当前页面的域名和设置的域名不一致,会设置失败
- secure:如果不设置,可以通过 http 协议加载文件设置,或者https加载文件设置,设置之后只能通过https设置,增加了安全性
火狐浏览器支持本地缓存 cookie,谷歌 chrome 不支持本地加载的 cookie
Session Storage
详情可以看 3.2 章节
sessionStorage
与 localStorage
的接口类似,但保存数据的生命周期与 localStorage
不同。 Session 这个词的意思,直译过来是"会话"。它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当浏览器关闭后,sessionStorage 中的数据就会被清空。一般结合后台使用。
- 可以达到 5M 大小
用户的操作
地址栏访问,会触发浏览器的缓存操作
F5 刷新,浏览器会设置 max-age = 0
跳过强制缓存,进行协商缓存判断
ctrl + F5
会跳过所有的缓存机制,直接从服务器拉去资源