Skip to main content

浏览器缓存机制

一、概述

1. 什么是缓存

简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个URL地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。

2. 缓存机制

简化流程吐下:

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

3. 缓存的好处

  • 减少网络延迟,加快页面打开速度
  • 减少网络带宽消耗
  • 降低服务器压力

二、强缓存

浏览器缓存一般分为两类:强缓存(也称本地缓存)和协商缓存(也称弱缓存)。

  • 强缓存:ExpiresCache-Control
  • 协商缓存:Last-Modified / If-Modified-SinceEtag / If-None-Match

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:

1. Expires

Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。

Expires: Wed, 22 Nov 2019 08:41:00 GMT

表示资源在2019年11月22号8点41分过期,过期了就得向服务端发请求。

到了HTTP/1.1,Expire已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存则会直接失效,这样的话强制缓存的存在则毫无意义,那么Cache-Control又是如何控制的呢?

2. Cache-Control

HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:

  • public:所有内容都将被缓存(客户端和代理服务器都可缓存)
  • private:所有内容只有客户端可以缓存,Cache-Control的默认取值
  • no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
  • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
  • max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效

需要注意,Cache-Control优先级比Expires优先级高,当ExpiresCache-Control同时存在的时候,Cache-Control会优先考虑。

三、协商缓存

强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存

1. Last-Modified / If-Modified-Since

Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间

If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件

2. Etag / If-None-Match

Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)

If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200

注:Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。

3. 两者对比

  1. 在精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:

    • 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
    • Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。
  2. 在性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 Etag需要根据文件的具体内容生成哈希值。

4. 浏览器HTTP请求过程

第一次请求

第一次请求

再次请求

再次请求

四、缓存位置

命中缓存之后,我们需要在缓存中提取数据,缓存位置如下从高到低:

  • Memory Cache(内存缓存)
  • Service Worker(离线缓存)
  • Disk Cache(磁盘缓存)
  • Push Cache(推送缓存)—— HTTP2 新特性

1. Memory Cache

Memory Cache指的是内存缓存,从效率上讲它是最快的。但是从存活时间来讲又是最短的,当渲染进程结束后,内存缓存也就不存在了。因为内存空间比较小,通常较小的资源放在内存缓存中,比如 base64 图片等资源

2. Disk Cache

Disk Cache就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是他的优势在于存储容量和存储时长。稍微有些计算机基础的应该很好理解,就不展开了。

浏览器如何决定将资源放进内存还是硬盘呢?主要策略如下:

  • 比较大的JS、CSS文件会直接被丢进磁盘,反之丢进内存
  • 内存使用率比较高的时候,文件优先进入磁盘

3. Service Worker

Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直 接访问 DOM 。 可以帮我们实现离线缓存、消息推送和网络代理等功能

4. Push Cache

即推送缓存,这是浏览器缓存的最后一道防线。它是 HTTP/2 中的内容,虽然现在应用的并不广泛,但随着 HTTP/2 的推广,它的应用越来越广泛。

五、用户行为对浏览器缓存的影响

  1. 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
  2. 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache
  3. 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control:no-cache(为了兼容,还带了 Pragma:no-cache),服务器直接返回 200 和最新内容。

六、参考文档