目录
什么是浏览器缓存
浏览器缓存的分类
🎗️ 强缓存
🎗️ 协商缓存
👉🏻 缓存请求流程
👉🏻 为什么要有Etag
👉🏻 缓存优先级
👉🏻 启发式缓存
👉🏻 其他补充
👉🏻 注意场景
用户行为对缓存的影响
什么是浏览器缓存
缓存是指代理服务器或客户端本地磁盘内保存的资源副本,利用缓存可减少对源服务器的访问,节省通信流量和通信时间。
浏览器缓存(Brower Caching)指的就是浏览器在本地磁盘对用户最近请求过的文档(文件)进行存储(缓存),以便在下一次访问时重复使用,即当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档,从而节省带宽、提升访问速度、降低服务器压力。
本文所说的HTTP缓存机制就是利用HTTP响应头将所请求的资源在浏览器中进行缓存。
当我们在浏览器中打开一个页面时,浏览器会根据你输入的URL到对应的服务器上请求你想要的数据资源。但这个过程中可能页面可能需要等待一段时间(白屏时间)才能渲染到你的页面中。
浏览器缓存的优点有:
1. 减少了冗余的数据(重复资源)传输,节省了网费
2. 减少了服务器的负担(开销),大大提升了网站的性能
3. 加快了(提高)客户端加载网页的速度
在前端开发面试中,浏览器缓存是web性能优化面试题中很重要的一个知识点,从而说明浏览器缓存是提升web性能的一大利器,但是浏览器缓存如果使用不当,也会产生很多问题,正所谓是,想说爱你,并不是很容易的事。
所以,结合最近遇到的案例,本文对浏览器缓存相关的知识进行总结归纳,希望对大家有所帮助。
浏览器缓存的分类
浏览器缓存方式主要有两类:缓存协商(对比缓存)和彻底缓存,也有称之为协商缓存和强缓存。 浏览器在第一次请求发生后,再次请求时:
1. 浏览器会先获取该资源缓存的header信息,根据其中的expires和cahe-control判断是否命中强缓存,若命中则直接从缓存中获取资源,包括缓存的header信息,本次请求不会与服务器进行通信;
2. 如果没有命中强缓存,浏览器会发送请求到服务器,该请求会携带第一次请求返回的有关缓存的header字段信息(Last-Modified / IF-Modified-Since、Etag / IF-None-Match),由服务器根据请求中的相关header信息来对比结果是否命中协商缓存,若命中,则服务器返回新的响应header信息更新缓存中的对应header信息,但是并不返回资源内容,它会告知浏览器可以直接从缓存获取;否则返回最新的资源内容
🎗️ 强缓存
强缓存指的是在缓存时间内不会向服务器发起请求,只有过期之后才会向服务器发起请求,整个流程如下所示:
在浏览器中,强缓存分为
Expires
(http1.0规范)、cache-control
(http1.1规范)两种。
强缓存是利用http的返回头(响应头)中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间。
Expires
Expires 该字段是http1.0时的规范,用于表示资源的过期时间的请求头字段,它的值为一个绝对时间的GMT格式的时间字符串,是由服务器端返回的。比如,Expires:Mon,18 Oct 2066 23:59:59 GMT。这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以,当服务器与客户端时间偏差较大时,就会导致缓存混乱。
在浏览器第一次请求资源时,服务器端的响应头会附上
Expires
这个响应字段,当浏览器在下一次请求这个资源时会根据上次的Expires
字段是否使用缓存资源(当请求时间小于服务端返回的到期时间,直接使用缓存数据)
expires是根据本地时间来判断的,假设客户端和服务器时间不同,会导致缓存命中误差
Cache-Control
我们已经知道了,上面我们提到的Expires
有个缺点,当客户端本地时间被修改时浏览器会直接向服务器请求新的资源,为了解决这个问题,在http1.1
规范中,提出了cache-control
字段,且这个字段优先级高于上面提到的Expires
,值是相对时间。
Cache-Control是http1.1时出现的header信息,主要是利用该字段的max-age值来进行判断,它是一个相对时间,例如,Cache-Control:max-age=3600,代表着资源的有效期是3600秒。
cache-control除了该字段外,还有下面几个比较常用的设置值:
1. no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
2. no-store:直接禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
3. public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
4. private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。
Cache-Control与Expires可以在服务端配置同时启用,同时启用的时候Cache-Control优先级高。
总结一下 cache-control 常见的属性值:
属性值 | 值 | 备注 |
---|---|---|
max-age | 3600 | 例如,值为3600,表示(当前时间+3600秒)内不与服务器请求新的数据资源 |
s-maxage | 和max-age一样,但这个是设定代理服务器的缓存时间 | |
private | 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存) | |
public | 所有内容都将被缓存(客户端和代理服务器都可缓存) | |
no-store | 不缓存任何数据 | |
no-cache | 储存在本地缓存区中,只是在与原始服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用 |
🎗️ 协商缓存
协商缓存都会向服务器发送请求,判断缓存数据是否过期,过期的话会返回新的内容,没有过期则使用本地的缓存数据。对于协商缓存主要利用两个字段:Last-Modify、Etag。
上面提到的强缓存都是由本地浏览器在确定是否使用缓存,当浏览器没有命中强缓存时就会向浏览器发送请求,验证协商缓存是否命中,如果缓存命中则返回304状态码,否则返回新的资源数据。
协商缓存就是由服务器来确定缓存资源是否可用,所以客户端与服务器端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问,这主要涉及到下面两组header字段,这两组搭档都是成对出现的,即浏览器第一次发出请求的响应头带上某个字段(Last-Modified或者Etag),则后续请求则会带上对应的请求字段(If-Modified-Since或者If-None-Match),若响应头没有Last-Modified或者Etag字段,则请求头也不会有对应的字段。
Last-Modify/If-Modify-Since 浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT。 当浏览器再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。 如果命中缓存,则返回304,并且不会返回资源内容,并且不会返回Last-Modify。
ETag/If-None-Match 与Last-Modify/If-Modify-Since不同的是,Etag/If-None-Match返回的是一个校验码。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。服务器根据浏览器上送的If-None-Match值来判断是否命中缓存。 与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化。
🧤 小结:协商缓存是由服务器来确定资源是否可用,这将涉及到两组字段成对出现的,在浏览器第一次发出请求时会带上字段(Last-Modified
或者Etag
),则后续请求则会带上对于的请求字段(if-modified-since
或者if-none-Match
),若响应头没有Last-Modified
或者Etag
,则请求头也不会有对应的字段。
Last-modified
表示本地文件最后修改时间,由服务器返回if-modified-since
是浏览器在请求数据时返回的,值是上次浏览器返回的Last-modifiedETag
是一个文件的唯一标识符,当资源发生变化时这个ETag
就会发生变化。弥补了上面last-modified
可能出现文件内容没有变化但是last-modified
发生了变化出现重新向服务器请求资源情况。这个值也是又服务器返回的if-none-match
是浏览器请求数据时带上的字段,值是上次服务器返回的ETag
如果你觉得还是有点云里雾里,可以参看以下请求流程图,相信可以很快明白何为协商缓存
👉🏻 缓存请求流程
我们结合强缓存,来捋一下浏览器缓存具体的请求流程
1. 当浏览器发起一个资源请求时,浏览器会先判断本地是否有缓存记录,如果没有会向浏览器请求新的资源,并记录服务器返回的last-modified
。
2. 如果有缓存记录,先判断强缓存是否存在(cache-control
优先于expires
,后面会说),如果强缓存的时间没有过期则返回本地缓存资源(状态码为200)
3. 如果强缓存失效了,客户端会发起请求进行协商缓存策略,首先服务器判断Etag
标识符,如果客户端传来标识符和当前服务器上的标识符是一致的,则返回状态码 304 not modified
(不会返回资源内容)
4. 如果没有Etag
字段,服务器会对比客户端传过来的if-modified-match
,如果这两个值是一致的,此时响应头不会带有last-modified
字段(因为资源没有变化,last-modified
的值也不会有变化)。客户端304状态码之后读取本地缓存。如果last-modified
。
5. 如果Etag
和服务器端上的不一致,重新获取新的资源,并进行协商缓存返回数据。
👉🏻 为什么要有Etag
你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难以解决的问题:
1. 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET。
也就是说,在没有修改文件内容情况下文件的最后修改时间可能也会改变,这会导致客户端认为这文件被改动了,从而重新请求;
2. 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)。使用 Etag
就能够保证这种需求下客户端在 1 秒内能刷新多次。
3. 某些服务器不能精确的得到(获取)文件的最后修改时间。
Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。 强缓存与协商缓存的区别可以用下表来表示:
缓存类型 | 获取资源形式 | 状态码 | 发送请求到服务器 |
---|---|---|---|
强缓存 | 从缓存取 | 200(from cache | 否,直接从缓存取 |
协商缓存 | 从缓存取 | 304(Not Modified) | 否,通过服务器来告知缓存是否可用 |
状态码区别
- 200 请求成功,服务器返回全新的数据
- 200
from memory cache / from disk cache
本地强缓存还在有效期,直接使用本地缓存 - 304 请求成功,走了协商缓存,服务器判定(
Etag
和Last-modified
)没有过期,告知浏览器使用缓存
from memory cache 是页面刷新的时候内存取的。from disk cache 页面标签关闭后从磁盘取的
👉🏻 缓存优先级
expires
和cache-control
如果同时存在时,cache-control
会覆盖expires
,expires
无效,无论是否过期,即 Cache-control > expires
强缓存和协商缓存如果同时存在时,会去先对比强缓存是否还再有效期,如果强缓存生效则对比协商缓存,即强缓存 > 协商缓存
协商缓存Etag
和last-modified
同时存在时,会先比较Etag
,last-modified
无效,即Etag > last-modified
扩展补充:
在http1.0规范时还有一个Pragma缓存策略,那时候Cache-control(http1.1)还没出来,它与 Cache-Control: no-cache 效果一致。强制要求缓存服务器在返回缓存的版本之前将请求提交到源头服务器进行验证。
◾ 关于Pragma:
是一个在 HTTP/1.0 中规定的通用首部,这个首部的效果依赖于不同的实现,所以在“请求 - 响应”链中可能会有不同的效果。它用来向后兼容只支持 HTTP/1.0 协议的缓存服务器,那时候 HTTP/1.1 协议中的 Cache-Control 还没有出来。
Pragma备注: 由于 Pragma 在 HTTP 响应中的行为没有确切规范,所以不能可靠替代 HTTP/1.1 中通用首部 Cache-Control,尽管在请求中,假如 Cache-Control 不存在的话,它的行为与 Cache-Control: no-cache 一致。建议只在需要兼容 HTTP/1.0 客户端的场合下应用 Pragma 首部。
paragma -> Cache-control -> expires -> Etag -> last-modified
👉🏻 启发式缓存
这个会缓存策略是浏览器默认的,如果发送一个网络请求没有expires
、cache-control
,但是又有last-modified
字段,那么在这种情况下浏览器会有一个默认缓存策略(currentTime - last-modified )*0.1
只有服务端没有返回明确的缓存策略时才会激活浏览器的启发式缓存策略
HTTP Heuristic Caching (Missing Cache-Control and Expires Headers) Explained | Paul Calvano
👉🏻 其他补充
- 协商缓存想要配合强缓存使用,如果不开启强缓存使用,协商缓存没有意义
- 大部分web服务器默认开启协商缓存,且是同时开启
last-modified
和Etag
👉🏻 注意场景
1. 分布式系统里last-modified
需要保持一致,以免负载到不同的机器导致比对失败,从而返回新资源。
2. 分布式系统尽量关闭掉Etag
,因为每一台服务器生成的Etag
是不同的
用户行为对缓存的影响
用户操作 | Expires/Cache-Control | Last-Modied/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进回退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl+F5强制刷新 | 无效 | 无效 |
参考资料
前端百题斩之一文了解HTTP缓存 | Expires、Last-Modified、Etag缓存控制 |
304状态码详解(协商缓存) | 深入理解浏览器缓存机制 | 扒一扒浏览器缓存机制 |
HTTP相关知识点整理 | 浏览器的缓存机制 | 能不能说一说浏览器缓存 |