Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

缓存(二)——浏览器缓存机制:强缓存、协商缓存 #41

Open
amandakelake opened this issue Apr 3, 2018 · 26 comments
Open
Labels

Comments

@amandakelake
Copy link
Owner

@amandakelake amandakelake commented Apr 3, 2018

网上关于缓存的文章非常多,但大都比较片面,或者只对某块进行了深入,没有把它们联系起来,本着系统学习的态度,笔者进行了整理,写成一个小系列,方便自己也方便他人共同学习,有写的不对的地方欢迎指正。
缓存(一)——缓存总览:从性能优化的角度看缓存
缓存(二)——浏览器缓存机制:强缓存、协商缓存
缓存(三)——数据存储:cookie、Storage、indexedDB
缓存(四)——离线应用缓存:App Cache => Manifest
缓存(五)——离线应用缓存:Service Worker(还没写,先占坑)

有经验的同学可以直接去看第四部分的流程图,如果你能完全理解那个图,那这篇文章你可以不看了,当然,看看当复习也好

一、概述

良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度
通常浏览器缓存策略分为两种:强缓存和协商缓存

1、基本原理

  • 1)浏览器在加载资源时,根据请求头的expirescache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。
  • 2)如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modifiedetag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源
  • 3)如果前面两者都没有命中,直接从服务器加载资源

2、相同点

如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;

3、不同点

强缓存不发请求到服务器,协商缓存会发请求到服务器。

二、强缓存

强缓存通过ExpiresCache-Control两种响应头实现

1、Expires

Expires是http1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间,由服务器返回。
Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效

Expires: Wed, 11 May 2018 07:20:00 GMT

2、Cache-Control

Cache-Control 出现于 HTTP / 1.1,优先级高于 Expires ,表示的是相对时间

Cache-Control: max-age=315360000

题外tips
Cache-Control: no-cache不会缓存数据到本地的说法是错误的,详情《HTTP权威指南》P182
0ae591a1-f6fa-4236-8588-9d9e300f8ca7

Cache-Control: no-store才是真正的不缓存数据到本地
Cache-Control: public可以被所有用户缓存(多用户共享),包括终端和CDN等中间代理服务器
Cache-Control: private只能被终端浏览器缓存(而且是私有缓存),不允许中继缓存服务器进行缓存

4d1eeec3-1fc7-4347-a019-fcb2e9ba0b96

三、协商缓存

当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的http状态为304并且会显示一个Not Modified的字符串

协商缓存是利用的是【Last-Modified,If-Modified-Since】【ETag、If-None-Match】这两对Header来管理的

1、Last-Modified,If-Modified-Since

Last-Modified 表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来

但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag

2、ETag、If-None-Match

Etag就像一个指纹,资源变化都会导致ETag变化,跟最后修改时间没有关系,ETag可以保证每一个资源是唯一的

If-None-Match的header会将上次返回的Etag发送给服务器,询问该资源的Etag是否有更新,有变动就会发送新的资源回来
070b6284-e835-4470-ac6e-7e1892fab369

ETag的优先级比Last-Modified更高

具体为什么要用ETag,主要出于下面几种情况考虑:

  • 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
  • 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
  • 某些服务器不能精确的得到文件的最后修改时间。

四、整体流程图

672fb4ce-28f9-498d-9140-b3ff9f47d62f

五、几种状态码的区别

  • 200:强缓Expires/Cache-Control存失效时,返回新的资源文件
  • 200(from cache): 强缓Expires/Cache-Control两者都存在,未过期,Cache-Control优先Expires时,浏览器从本地获取资源成功
  • 304(Not Modified ):协商缓存Last-modified/Etag没有过期时,服务端返回状态码304

但是!但是!
现在的200(from cache)已经变成了from disk cache(磁盘缓存)from memory cache(内存缓存)两种
打开chrome控制台看一下网络请求就知道了
f6b341a3-45d7-4284-a454-6e0613a5560c
具体两者的区别,暂时没有去深究,有兴趣的同学可以自己去研究

六、如何选择合适的缓存

大致的顺序

  • Cache-Control —— 请求服务器之前
  • Expires —— 请求服务器之前
  • If-None-Match (Etag) —— 请求服务器
  • If-Modified-Since (Last-Modified) —— 请求服务器

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义

大部分web服务器都默认开启协商缓存,而且是同时启用【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】

但是下面的场景需要注意:

  • 分布式系统里多台机器间文件的Last-Modified必须保持一致,以免负载均衡到不同机器导致比对失败;
  • 分布式系统尽量关闭掉ETag(每台机器生成的ETag都会不一样);

七、后记

感谢您耐心看到这里,希望有所收获!

如果不是很忙的话,麻烦右上角点个star【Github博客传送门】,举手之劳,却是对作者莫大的鼓励。

我在学习过程中喜欢做记录,分享的是自己在前端之路上的一些积累和思考,希望能跟大家一起交流与进步,更多文章请看【amandakelake的Github博客】

参考

浅谈Web缓存 | AlloyTeam
浏览器缓存知识小结及应用 - 流云诸葛 - 博客园
HTTP 缓存  |  Web  |  Google Developers
大公司里怎样开发和部署前端代码? - 知乎
HTTP强缓存和协商缓存 - JavaScript学习笔记 - SegmentFault 思否

@pampang
Copy link

@pampang pampang commented Dec 13, 2018

看过这么多篇关于缓存的文章,还是博主的文章最为全面,各个方面都说到了。
例如很多文章就直接不说强缓存和协商缓存的优先级。

@amandakelake
Copy link
Owner Author

@amandakelake amandakelake commented Dec 13, 2018

谢谢你的认真阅读,这会让我更有动力的

@Xu-Angel
Copy link

@Xu-Angel Xu-Angel commented Jan 28, 2019

很齐全

@ty-cs
Copy link

@ty-cs ty-cs commented Mar 12, 2019

感谢总结!

@KentonYu
Copy link

@KentonYu KentonYu commented Jul 11, 2019

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义

协商缓存本身是有意义的,比如取一个 1G 的大文件,通过协商缓存,下次不一定要重新下载

@ItoshikiNozomu
Copy link

@ItoshikiNozomu ItoshikiNozomu commented Aug 7, 2019

最近了解到一个heuristic cache(启发式缓存)的概念,我觉得可以补充一下

如果Expires,Cache-Control: max-age,或 Cache-Control:s-maxage都没有在响应头中出现,并且设置了Last-Modified时,那么浏览器默认会采用一个启发式的算法,即启发式缓存。通常会取响应头的Date_value - Last-Modified_value值的10%作为缓存时间。

作者:NowhereToRun
链接:https://www.jianshu.com/p/5b8d61afe52d
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

@JackieZhang-Coding
Copy link

@JackieZhang-Coding JackieZhang-Coding commented Dec 3, 2019

在vue-cli2项目中使用了协商缓存,但是版本更新之后,打开浏览器有时候并不会去请求新资源,而是需要 CTRL + F5强刷,更有甚者需要清理浏览器缓存,不然会报 js 加载错误,Unexpected token <

@ethan-cao
Copy link

@ethan-cao ethan-cao commented Dec 9, 2019

请问 强缓存 和 协商缓存 的英文分别是什么呢?

完全找不到对应的英文资料啊

@singlelove
Copy link

@singlelove singlelove commented Dec 18, 2019

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义
何出此言?

@pyuyu
Copy link

@pyuyu pyuyu commented Jan 1, 2020

"但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag"
本地是指在客户端本地还是服务器本地呀

@ficapy
Copy link

@ficapy ficapy commented Apr 24, 2020

https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching

感觉强缓存协商缓存是中文社区造词讹传

原文里写的
Cache-Control:缓存控制
缓存验证机制: Etag, Last-Modified

中文社区里面造词强缓存,是不是能让人联想到弱缓存呢,然而并没有
验证缓存有效性,明显验证也比协商恰当

@pigpigever
Copy link

@pigpigever pigpigever commented May 12, 2020

写得很好,看完 《HTTP 权威指南》里关于缓存的章节,配合你这篇文章食用更佳~

@pdsuwwz
Copy link

@pdsuwwz pdsuwwz commented Jun 9, 2020

蟹博主, 学习了 ~

@lthon-tse
Copy link

@lthon-tse lthon-tse commented Jun 21, 2020

@ficapy @ethan-cao
我来根据 RFC 7234 Caching 来捋一捋这两个术语,抛砖引玉哈。

下面的引用有删减,详见 RFC 7234 中文翻译

HTTP 缓存,是一种响应消息的本地存储,以及控制其内的消息的存储、获取和删除的子系统。
在 HTTP/1.1 中,缓存通过复用一个之前的响应消息来满足当前的请求,其目的是显著提升性能。
一个已存储的响应,如果它在不需要“验证”的情况就可以用来复用,那么,它被认为是“新鲜的”。
对于所请求的一个 URI,当缓存已经存储了这个 URI 对应的一个或多个响应,但缓存却不能使用它们之中的任何一个来满足这个请求,这时候,缓存在转发这个请求时可以应用条件请求的机制,给下一个入站服务器一个机会来选择一个有效的已存储的响应来使用,在这个过程中会更新已存储的元数据;或者使用一个新的响应到替换(可能是多个)已存储的响应。这个过程称为对已存储的响应进行“验证”或者“重新验证”。

实际上,强缓存指的是新鲜的响应,而协商缓存指的是陈旧的响应(已过期)。而要复用陈旧的响应就得使用条件请求(If-xxx)进行缓存验证。
同觉得强缓存和协商缓存这两个术语词不达意,不太适合继续使用。

判断已缓存的响应是否过期的机制见 RFC 7234 4.2. 新鲜度 / Freshness。(与 Expires 和 Cache-Control 的 max-age 指令等值相关)
条件请求相关(If-xxx、ETag、Last-Modified、304/412)可以看 RFC 7232 中文翻译

@qianlongdoit
Copy link

@qianlongdoit qianlongdoit commented Aug 20, 2020

from mermory cache是页面刷新的时候内存取的
from disk cache 页面tab关闭后从磁盘取的
这就是区别吧

@TerryMinH
Copy link

@TerryMinH TerryMinH commented Sep 23, 2020

你好,想问下,浏览器强缓存与协商缓存如何配置呢

@wangyangyangyang
Copy link

@wangyangyangyang wangyangyangyang commented Sep 27, 2020

一篇好文章就是让人读懂,要是早点看到这篇文章就好了,那我大概就可以在百度二面时回答上这个问题了

@finalwhy
Copy link

@finalwhy finalwhy commented Nov 9, 2020

有个错误,在下面这段

1、基本原理

  • 1)浏览器在加载资源时,根据请求头的expirescache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。
  • 2)如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modifiedetag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源
  • 3)如果前面两者都没有命中,直接从服务器加载资源

在第一条,判断是否命中强缓存时校验的应该是资源的响应头里的expirescache-control两个字段吧?请求头里怎会有expires字段呢?

@heaven2049
Copy link

@heaven2049 heaven2049 commented Dec 13, 2020

MemoryCache 是指该文件内容仍在内存中,可以直接从内存取,速度更快。
Disk Cache 是指从用户缓存的硬盘中读取到内存

@Edmodo-Luo
Copy link

@Edmodo-Luo Edmodo-Luo commented Feb 26, 2021

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义

这句话是什么理解呢?没有强缓存,协商缓存就用不了吗

@HenryC-3
Copy link

@HenryC-3 HenryC-3 commented Feb 27, 2021

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义

这句话是什么理解呢?没有强缓存,协商缓存就用不了吗

别被绕糊涂了,强缓存和协商缓存二词非常有误导性,上面已经有人提及,建议直接查看文档。如果一定要借用这两个词的话,个人认为可以这样理解:

  • 强缓存:缓存没有过期,直接读取缓存
  • 协商缓存:缓存过期,借助 Etag 和 Last-Modified 验证缓存是否可用(即所谓“协商”),可用就读取缓存(读取缓存,即“强缓存”),不可用就使用服务端返回的新资源

没有强缓存,协商缓存就用不了吗

读取缓存是验证缓存可用后的最后一步,不读取,前面的验证自然也没了意义。

两个词都是对流程的概括,但是听起来像管理缓存的手段,强缓存容易产生“弱缓存”的联想,协商也和“验证”挂不上边。

如有错误,烦请指出

@Edmodo-Luo
Copy link

@Edmodo-Luo Edmodo-Luo commented Mar 1, 2021

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义
这句话是什么理解呢?没有强缓存,协商缓存就用不了吗

别被绕糊涂了,强缓存和协商缓存二词非常有误导性,上面已经有人提及,建议直接查看文档。如果一定要借用这两个词的话,个人认为可以这样理解:

  • 强缓存:缓存没有过期,直接读取缓存
  • 协商缓存:缓存过期,借助 Etag 和 Last-Modified 验证缓存是否可用(即所谓“协商”),可用就读取缓存(读取缓存,即“强缓存”),不可用就使用服务端返回的新资源

没有强缓存,协商缓存就用不了吗

读取缓存是验证缓存可用后的最后一步,不读取,前面的验证自然也没了意义。

两个词都是对流程的概括,但是听起来像管理缓存的手段,强缓存容易产生“弱缓存”的联想,协商也和“验证”挂不上边。

如有错误,烦请指出

这样说来,协商缓存读的还是强缓存,协商缓存就不能单独存在,只开协商缓存,可能还更慢?因为你多了一部校验的动作,结果还是要返回新资源

@finalwhy
Copy link

@finalwhy finalwhy commented Mar 1, 2021

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义
这句话是什么理解呢?没有强缓存,协商缓存就用不了吗

别被绕糊涂了,强缓存和协商缓存二词非常有误导性,上面已经有人提及,建议直接查看文档。如果一定要借用这两个词的话,个人认为可以这样理解:

  • 强缓存:缓存没有过期,直接读取缓存
  • 协商缓存:缓存过期,借助 Etag 和 Last-Modified 验证缓存是否可用(即所谓“协商”),可用就读取缓存(读取缓存,即“强缓存”),不可用就使用服务端返回的新资源

没有强缓存,协商缓存就用不了吗

读取缓存是验证缓存可用后的最后一步,不读取,前面的验证自然也没了意义。
两个词都是对流程的概括,但是听起来像管理缓存的手段,强缓存容易产生“弱缓存”的联想,协商也和“验证”挂不上边。
如有错误,烦请指出

这样说来,协商缓存读的还是强缓存,协商缓存就不能单独存在,只开协商缓存,可能还更慢?因为你多了一部校验的动作,结果还是要返回新资源

其实我认为与其说强缓存协商缓存,还不如说成缓存控制缓存校验更直观一点

@Edmodo-Luo
Copy link

@Edmodo-Luo Edmodo-Luo commented Mar 2, 2021

Cache-Control: max-age=0, must-revalidate
这个头部配置下,是属于开启了协商缓存,但是强缓存永远无法命中?

@Yanhua67
Copy link

@Yanhua67 Yanhua67 commented Mar 30, 2021

Cache-Control: max-age=0, must-revalidate
这个头部配置下,是属于开启了协商缓存,但是强缓存永远无法命中?

我是这样理解的,max-age=0表示本地缓存一直处于过期的状态,不代表不会缓存,must-revalidate表示本地缓存过期之后都会向服务器发送请求进行缓存校验。如果具有缓存校验(etag、last-modified)会向服务器发送请求检查资源是否发生变化,如果服务器资源未变化,请求状态码为304使用本地缓存,否则请求为200并刷新本地缓存资源。

@macc6579
Copy link

@macc6579 macc6579 commented Oct 11, 2021

受教了,感谢分享~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet