一、缓存定义
定义:利用浏览器存储机制,将一部分数据保存在客户端,从而减少对服务器的请求降低服务器的压力,提升效率。
为什么需要http缓存:
- 为了减少网络请求的数量和体积,让页面加载更在方便
- 当网络不稳定时,仍可以使用缓存的资源进行展示
开源浏览器引擎的资源可以分为:
- 主资源,比如HTML页面,或者下载项
- 派生资源,分类如下
- Javascript脚本
- CSS样式
- 图片
- 字体
- ...
可以说除了主资源之外,剩下的网络资源都是派生资源,而浏览器缓存的就是派生资源。
二、缓存位置
从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。
Service Worker:运行在浏览器背后的独立线程,它可以自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的Memory Cache:基于内存的缓存,读取高效速度快,但是一旦关闭网页(Tab标签页面),内存就释放了。Disk Cache:基于磁盘的缓存,容量大,读取慢Push Cache:推送缓存,http2中的内容,缓存在会话session中的,一旦会话结束就被释放,并且缓存时间也很短暂
三、缓存策略
如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。
- 第一次请求过后,浏览器即客户端,已经在本地及内存中存储了一份带有第一次请求资源返回的资源内容,头部信息,请求地址等的一份文件,文件图片如上。
- 如果再次请求资源地址时,首先会查询缓存目录下是否有该请求对应的文件,若没有,则直接对服务器发起请求,如有该文件,则说明有缓存
总结: 由上图我们可以知道:
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。
-
强缓存:
不会向服务器发送请求,直接从缓存中读取资源,强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-ControlExpires (过期时间):
用来指定资源到期的时间如果发送请求的时间早于expires时间,则本地缓存始终有效,否则才会向服务器重新发起请求。
Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效Cache-Control:
缓存资源的最大过期时间,通过浏览器时间的date值 + max-age/s-maxage的秒数与当前时间进行计算,判断是否过期。
HTTP/1.1 的产物,比如当设置Cache-Control:max-age=300,单位是s,代表5分钟内再次请求就会走强缓存
对比: Cache-Control优先级高于Expires
-
协商缓存:
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETagLast-Modified/If-Modified-Since:
http1.0
原理:浏览器第一次访问资源时,服务器会在response头里添加Last-Modified时间点,这个时间点是服务器最后修改文件的时间点,然后浏览器第二次访问资源时,检测到缓存文件里有Last-Modified,就会在请求头里加If-Modified-Since,其值为Last-Modified的值,服务器收到头里有If-Modified-Since,就会拿这个值和请求文件的最后修改时间作对比,如果没有变化,就返回304(缓存重定制),如果小于了最后修改时间,说明文件有更新,就会返回新的资源,状态码为200ETag/If-None-Match:
http1.1
原理:与Last-Modified类似,只是Last-Modified返回的是最后修改的时间点,而ETag是每次访问服务器都会返回一个新的token,第二次请求时,该值将请求头里的If-None-Match(值为之前服务器端返回的资源上的ETag)发送给服务器,服务器在比较新旧的token是否一致,一致则返回304通知浏览器使用本地缓存,不一致则返回新的资源,新的ETag,状态码为200。
对比: ETag更精确,性能上Last-Modified好点
-
如果什么缓存策略都不设置:
这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间
ETag属性之间的比较采用的是弱比较算法,即两个文件除了每个比特都相同外,内容一致也可以认为是相同的。例如,如果两个页面仅仅在页脚的生成时间有所不同,就可以认为二者是相同的。
因为ETag的特性,所以相较于Last-Modified有一些优势:
1. 某些情况下服务器无法获取资源的最后修改时间
2. 资源的最后修改时间变了但是内容没变,使用ETag可以正确缓存
3. 如果资源修改非常频繁,在秒以下的时间进行修改,Last-Modified只能精确到秒
四、实际应用场景
1.频繁变动的资源
Cache-Control: no-cache
对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
2.不常变化的资源
Cache-Control: max-age=31536000
通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。 在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。
五、用户行为对浏览器缓存的影响(有争议)
所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:
- 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。(强制缓存有效,协商缓存有效)
- 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。(强制缓存失效,即不会去判定相关header是否过期,协议缓存有效)
- 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有
Cache-control: no-cache(为了兼容,还带了Pragma: no-cache),服务器直接返回 200 和最新内容。(强制缓存失效,协商缓存失效,全部缓存失效,全部资源重新发起请求)
参考文章: 浏览器缓存策略、http缓存机制、浏览器的缓存策略、浏览器HTTP缓存机制