谈谈浏览器的缓存机制
概述
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。在开发过程中随处可见,小到
Javasript
作用域内的变量存储,大到使用CDN使静态资源的分布存储到缓存服务器上等。缓存的简单解释就是将我们的计算结果储存,当下一次计算时,可以将保存的结果值快速返回。(类似于CPU寄存器)
什么是浏览器缓存
浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对静态资源进行存储,当再次请求该页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。而浏览器缓存,本质上就是HTTP的缓存。
HTTP缓存就是将第一次请求的结果存储起来,当下一次请求发起时,可以快速匹配对应的缓存文件。这样就节省了请求的时间,达到了提高网页效率的目的。
来看看缓存简单应用的流程图:
图中,假设先不考虑缓存过期的问题,对同一个请求,发起了两次:
- 第一次是未匹配到缓存资源,因此又向服务端发起了一次真实的请求,返回了对应的响应结果以及携带了缓存相关的标识(后面会讲)。
- 第二次从缓存中匹配到资源结果,因此,就省去了想服务端发送的请求,直接从缓存中返回对应的结果。
对比两次请求的时间,可以看到缓存带来的优点:
- 加快页面加载速度 从缓存中的请求时长少于服务器发起的请求时长
- 减少网络延迟和带宽消耗 读取本地缓存
- 降低服务端压力
问题
看到这里,我们就可能会产生一些疑问:
- 浏览器缓存都有哪些类型
- 浏览器的缓存机制
- 服务端返回的响应中,有带有哪些缓存的规则
- 浏览器发起的请求体中,有会带上哪些缓存的标识
- 如何判断缓存是否有效
下面我们就来逐一讨论
浏览器缓存都有哪些类型
按存储位置分类
浏览器缓存按照缓存储存位置分类,可以参考下图为Twitter主页的资源类型:
可以看到资源类型分为四种:
- ServiceWorker
- memory cache 读取内存缓存资源
- disk cache 读取本地磁盘缓存资源
- 无缓存的正常请求
从类型上我们可以简单进行读取速度的比较(Seriveworker后面会讲):
memory cache > disk cache > 网络请求
memory cache
顾名思义: 就是浏览器内存中的缓存,因此相比读取磁盘速度更快。
思考一下,是不是所有的资源都可以存储到memory cache,那么我们的页面不就是飞速的提升?
答案是:想多了。单个页面请求数量过大,但是浏览器的占用内存又不是无限制的,势必导致memory cache是一个短期存储器。
- memory cache的生命周期也很短暂,当关闭一个tab栏时,浏览器会自动释放对应页面的缓存。
disk cache
- disk cache就是储存在磁盘中的缓存,属于持久化存储,是真实存储在计算机的文件系统中的。
- disk cache缓存的有效期需要通过
HTTP
头信息来判断(后面会讲),并且缓存过期后,不同浏览器会有自己的算法识别过期资源并进行清理。
请求网络
在前述所有的缓存中都没有命中,浏览器就会发起HTTP
网络请求。
ServiceWorker
概念
这个 API 的唯一目的就是解放主线程,
Web Worker
是脱离在主线程之外的,将一些复杂的耗时的活交给它干,完成后通过postMessage
方法告诉主线程,而主线程通过onMessage
方法得到Web Worker
的结果反馈。
功能和特性
Service Worker
拥有自己独立的worker
线程,独立于当前网页线程- 离线缓存静态资源
- 拦截代理请求和响应
- 可自定义响应内容
- 可以通过
postMessage
向主线程发送消息 - 无法直接操作DOM
- 必须在HTTPS环境下工作或 localhost / 127.0.0.1 (自身安全机制)
- 通过
Promise
异步实现 Service Worker
安装(installing
)完成后,就会一直存在,除非手动卸载(unregister
)
参考自: ServiceWorker 缓存离线化
优先级
当浏览器发起一个请求之后,首先会从serviceWorker
中的cacheStorage
中查询,如果命中则返回对应资源,没有继续从memory cache
中查找,以此类推,直到所有缓存都没有命中,发起一个HTTP
网络请求。
-
首次访问页面
-
刷新该页面
可以看到,对应的资源文件已经储存到memory cache
- 关闭tab页,重新打开该页面访问
静态资源文件已经全部从disk cache中获取
因此,我们就可以看到三者的缓存读取的顺序了。
按缓存失效分类
按缓存失效后采取的措施,可以分为: 强缓存和协商缓存。
强缓存
强缓存就是本地缓存,即前文提到的disk cache。影响强制缓存的两个字段分别是: Cache-control 和 Expires。
常见的Cache-control设置有:
Cache-Control: max-age=<seconds> 设置缓存存储的最大周期
Cache-Control: max-stale[=<seconds>] 表明客户端愿意接收一个已经过期的资源
Cache-Control: min-fresh=<seconds> 表示客户端希望在指定的时间内获取最新的响应
Cache-control: no-cache 在释放缓存副本之前,强制高速缓存将请求提交给原始服务器进行验证
Cache-control: no-store 缓存不应存储有关客户端请求或服务器响应的任何内容
Cache-control: no-transform 不得对资源进行转换或转变
Cache-control: only-if-cached 表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝
对比缓存
当本地缓存过期之后,请求服务端之后,服务端对比资源文件,下发304
状态码,表示当前本地缓存继续可用,于是客户端继续使用缓存。这类缓存读取与本地直接读取缓存多了一次请求过程。
对比缓存有两组字段:
Last-Modified & If-Modified-Since
- 服务器通过
Last-Modified
字段告知客户端,资源最后一次被修改的时间,例如Last-Modified: Mon, 10 Nov 2018 09:10:11 GMTLast-Modified: Mon, 10 Nov 2018 09:10:11 GMT
- 浏览器将这个值和内容一起记录在缓存数据库中。浏览器将这个值和内容一起记录在缓存数据库中。
- 下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的
Last-Modified
的值写入到请求头的If-Modified-Since
字段下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的Last-Modified
的值写入到请求头的If-Modified-Since
字段 - 服务器会将
If-Modified-Since
的值与Last-Modified
字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200状态码,并返回数据。服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200状态码,并返回数据。
- Etag & If-None-Match
Etag
存储的是文件的特殊标识(一般都是 hash
生成的),服务器存储着文件的 Etag
字段。之后的流程和 Last-Modified
一致,只是 Last-Modified
字段和它所表示的更新时间改变成了 Etag
字段和它所表示的文件 hash
,把 If-Modified-Since
变成了 If-None-Match
。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
Etag
的优先级高于 Last-Modified