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

浏览器缓存策略 #28

Open
dark9wesley opened this issue May 18, 2021 · 0 comments
Open

浏览器缓存策略 #28

dark9wesley opened this issue May 18, 2021 · 0 comments

Comments

@dark9wesley
Copy link
Owner

dark9wesley commented May 18, 2021

浏览器的缓存策略是由HTTP报文中与缓存相关的缓存标识(头部字段)来决定的。

常见的缓存标识有:

  1. Expires: 响应头,代表该资源的绝对过期时间。
  2. Cache-Control:请求/响应头,缓存控制字段,精确控制缓存策略。
  3. if-Modified-Sice:请求头,资源最近修改时间,也就是服务端上一次响应报文中的Last-Modified,由浏览器告诉服务端。
  4. Last-Modified:响应头,资源最近修改时间,由服务端告诉浏览器。
  5. if-None-Match: 请求头,资源唯一标识,也就是服务端上一次响应报文中的Etag,由浏览器告诉服务端。
  6. Etag:响应头,资源唯一标识,由服务端告诉浏览器。

缓存过程分析

HTTP基于请求-应答模型,也就是浏览器发起请求,服务端响应请求。

在浏览器第一次发起请求时,会根据返回的响应报文中的缓存标识,来决定是否缓存响应结果。如果是则将响应结果与缓存标识一起存入浏览器缓存中。

cache1

由上图可以知道:

  • 浏览器每次发起请求,都会先在浏览器缓存中查找是否有该请求的结果和缓存标识。
  • 浏览器每次收到响应,如果响应报文允许缓存,就会将该响应结果与缓存标识存入浏览器缓存中。

以上两点就是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取。

根据是否需要向服务器重新发起HTTP请求,可以将缓存过程分为两个部分,分别是:

  • 强缓存:向浏览器缓存查找该请求结果,如果有缓存结果且强缓存有效,则直接返回结果,不发起请求的过程。
  • 协商缓存:强缓存失效后,浏览器携带协商缓存标识发起HTTP请求,由服务端根据协商缓存标识来决定是否使用缓存的过程。

强缓存

强缓存决定了缓存能否直接使用,如果可以,则不需要发起请求,直接从缓存中读取资源,且会返回200的状态码。

控制强缓存的字段分别是ExpiresCache-Control,其中Cache-Control优先级比Expires高。

Expires(HTTP/1.0)

表示该资源的绝对过期时间,如果没过期就可以直接使用,强缓存生效,不需要发起请求。

Expires: Wed, 22 Oct 2018 08:41:00 GMT

缺点是Expires受限于客户端时间,如果修改了客户端时间,可能会造成缓存失效。

为了弥补这种缺点,HTTP/1.1增加了Cache-Control

Cache-Control(HTTP/1.1)

Cache-control: max-age=30

该属性值表示资源会在30秒后过期,需要再次请求。也就是说在30秒内如果再次发起该请求,则会直接使用缓存,强缓存生效。

它与Expires相比:

  • Expires的时间值,是一个绝对值。
  • Cache-Control中的max-age=600 ,是相对值,用来解决Expires受限于客户端时间的问题。

除了max-age之外,Cache-Control还有以下属性:

cache2

总结

Cache-Control 的优先级高于 Expires,为了兼容 HTTP/1.0 和 HTTP/1.1,实际项目中两个字段都会设置。

协商缓存

如果强缓存失效了,例如:

  • 没有 Cache-Control 和 Expires
  • Cache-Control 和 Expires 过期
  • 设置了 no-cache 或 max-age = 0

如果出现了上述三种情况,强缓存失效,那就需要进行协商缓存。

浏览器需要携带协商缓存标识重新发起HTTP请求,来验证服务器资源是否有更新,会有两种结果:

  • 有更新,返回200,更新缓存
  • 无更新,返回304,更新浏览器缓存有效期

控制协商缓存的的字段分别是:

  1. Last-Modified 和 If-Modified-Since
  2. Etag 和 If-None-Match
    其中Etag/If-None-Match的优先级比Last-Modified/If-Modified-Since高

Last-Modified 和 If-Modified-Since(HTTP/1.0)

  • Last-Modified(响应头)
  • If-Modified-Since(请求头)

Last-Modified表示该资源文件最后修改日期,If-Modified-Since 会将之前收到的 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来并更新缓存,否则返回 304 状态码,表示可以继续使用缓存内容,并更新缓存有效期。

但这种根据更改时间的方式存在一些缺陷,例如:

  1. 如果服务端在1s内多次更新了这个资源,因为时间是秒级,从时间来看是无法分辨出是否有更新的。
  2. 如果该资源是定期更新,但有时会是同样的内容,实际上没有变化,用修改时间就会误以为发生了变化,传送给浏览器就会浪费带宽。

Etag 和 If-None-Match(HTTP/1.1)

为了解决Last-Modified 和 If-Modified-Since存在的问题,HTTP/1.1新增了这组缓存标识。

  • ETag(响应头)
  • If-None-Match(请求头)

ETag类似资源指纹,是资源的唯一标识。当资源有变化时,Etag就会重新生成,If-None-Match 会将之前收到的 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来并更新缓存,否则返回 304 状态码,表示可以继续使用缓存内容,并更新缓存有效期。

使用ETag就可以精确地识别资源的变动情况,就算是秒内的更新,也会让浏览器感知,能够更有效地利用缓存。

ETag的强弱

ETag同时支持强校验和弱校验。它们通过ETag标识符的开头是否存在“W/”来区分,如

"123456789"   -- 一个强ETag验证符
W/"123456789"  -- 一个弱ETag验证符

强 ETag 要求资源在字节级别必须完全相符,弱 ETag 在值前有个“W/”标记,只要求资源在语义上没有变化。

缓存位置

既然浏览器缓存了资源,那么资源会被缓存在哪呢?

cache3

根据上图浏览器控制台Network中的Size栏,可以看到有两个缓存位置:

  1. memory cache
  2. disk cache

memory cache(内存缓存)

内存缓存具有两个特点,分别是快速读取和时效性:

  1. 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
  2. 时效性:一旦该进程关闭,则该进程的内存则会清空。

disk cache(硬盘缓存)

硬盘缓存是直接将缓存写入硬盘文件中,读取缓存需要对硬盘文件进行I/O操作,然后重新解析该缓存内容,读取速度比内存缓存慢。

资源的缓存位置

既然有两个缓存位置,那么哪些资源会被放在内存缓存?哪些资源会被放在硬盘缓存?

类型 说明
memory cache(内存缓存) 存储需要频繁读取的文件,一般是脚本、字体、图片等资源
disk ceche(硬盘缓存) 存储不需要频繁读取的文件,一般是非脚本资源,如css等

因为CSS文件加载一次就可以渲染出来,我们不会频繁读取它,所以它不适合缓存到内存中。但是js之类的脚本却随时可能会执行,如果脚本在磁盘当中,我们在执行脚本的时候需要从磁盘取到内存中来,这样I/O开销就很大了,有可能导致浏览器失去响应。

访问缓存优先级

访问缓存遵循以下原则:

  1. 先在内存缓存中查找,如果有且强缓存有效,直接加载。
  2. 如果内存缓存中不存在,则在硬盘缓存中查找,如果有且强缓存有效直接加载。
  3. 如果硬盘缓存中也没有,则进行网络请求。
  4. 将请求获取的资源和缓存标识存到硬盘和内存。

缓存方案

大多项目都使用以下缓存方案:

  • HTML: 协商缓存;
  • css、js、图片:强缓存,文件名带上hash。

参考

彻底理解浏览器的缓存机制
实践这一次,彻底搞懂浏览器缓存机制

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

No branches or pull requests

1 participant