HTTP 缓存机制 强制缓存/协商缓存 Expires 和 Cache-control / Last-Modified 和 Etag

本文深入探讨了HTTP缓存机制,包括强缓存和协商缓存,通过动画和实例解释了如何命中缓存以及何时生效。重点讨论了Cache-Control和Expires等首部字段在控制缓存中的作用,以及如何设置和管理缓存以优化前端性能。此外,还提到了缓存的位置,如MemoryCache、ServiceWorker和DiskCache。
摘要由CSDN通过智能技术生成

为什么被缓存,如何命中缓存以及缓存什么时候生效的,我们却很少在实际开发中去了解。借助动画形式来从根上理解 HTTP 缓存机制及原理。

HTTP 缓存,对于前端的性能优化方面来讲,是非常关键的,从缓存中读取数据和直接向服务器请求数据,完全就是一个在天上,一个在地下。

我们最熟悉的是 HTTP 服务器响应返回状态码 304,304 代表表示告诉浏览器,本地有缓存数据,可直接从本地获取,无需从服务器获取浪费时间。

如果缓存过期,则继续从服务器验证 

为什么会有缓存?


单纯的从计算机角度去说,比较抽象,咱们看一个实际的例子。比如,我们通常喜欢把没看完的书放在书架上,而看完以及没有看的书放在箱子中保存。

如果我们把所有的书保存在箱子中,每次看书都要去箱子中找,所以非常麻烦和耗时(这里的箱子,可以想象成服务器)。

当我们开始看新书时,第一次从箱子中取出,看了一半,然后我们直接放到书架上,当下次再看书的时候,直接从书架中取出,这里的书架,就是我们下边要讲到的缓存(一个缓存仓库)。

Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。

浏览器缓存也包含很多内容: HTTP 缓存、indexDB、cookie、localstorage 等等。这里我们只讨论 HTTP 缓存相关内容。

在具体了解 HTTP 缓存之前先来明确几个术语:

  • 缓存命中率:从缓存中得到数据的请求数与所有请求数的比率,理想状态是越高越好。
  • 过期内容:超过设置的有效时间,被标记为“陈旧”的内容。通常过期内容不能用于回复客户端的请求,必须重新向源服务器请求新的内容或者验证缓存的内容是否仍然准备。
  • 验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。
  • 失效:失效就是把内容从缓存中移除。当内容发生改变时就必须移除失效的内容。

浏览器缓存主要是 HTTP 协议定义的缓存机制。HTML meta 标签,例如:

<META HTTP-EQUIV="Pragma" CONTENT="no-store">
Cache-Control:no-store

含义是让浏览器不缓存当前页面,但是代理服务器不解析 HTML 内容,一般应用广泛的是用 HTTP 头信息控制缓存。

HTTP 头信息控制缓存


大致分为两种:强缓存和协商缓存,强缓存如果命中缓存不需要和服务器端发生交互,而协商缓存不管是否命中都要和服务器端发生交互,强制缓存的优先级高于协商缓存。具体内容下文介绍。

Cache-Control:通过指定首部字段 Cache-Control 的指令,就能操作缓存的工作机制。

图:首部字段 Cache-Control 能够控制缓存的行为,指令的参数是可选的,多个指令之间通过“,”分隔。

首部字段Cache-Control 的指令可用于请求及响应时。Cache-Control: private, max-age=0, no-cache

匹配流程(已有缓存的情况下):

缓存的“龟”则

当浏览器发出请求到数据请求回来的过程,就像是上述中的取书过程。

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

动画演示:

强缓存


可以理解为无须验证的缓存策略。对强缓存来说,响应头中有两个字段 Expires/Cache-Control 来表明规则。

强缓存的意思就是不向服务器发起请求的缓存,也就是本地强制缓存。浏览器想要获取特定数据的时候,首先会检查一下本地的缓存是否存在该数据,如果存在,就直接在本地获取了,如果不存在,就向服务器所要该数据。

详细请求过程如下动画所示: 

那么问题来了,如果我们想使用强缓存,那怎么判断缓存数据什么时候失效呢?

当浏览器向服务器请求数据的时候,服务器会将数据和缓存的规则返回,在响应头的 header 中,有两个字段 Expires和Cache-Control。

(1)Expires

expires: Wed, 11 Sep 2019 16:12:18 GMT 

在响应头中 Expires 字段的意思是,当前返回数据的缓存到期时间戳。当浏览器在进行请求的时候,会那浏览器本地的时候和这个时间做对比,判断资源是否过期。

但是上述存在一个问题就是,如果我手动改变了电脑的时间,那么就会出现问题,这也是 HTTP1.0 中存在的问题。

Expires 指缓存过期的时间,超过了这个时间点就代表资源过期。有一个问题是由于使用具体时间,如果时间表示出错或者没有转换到正确的时区都可能造成缓存生命周期出错,并且 Expires 是 HTTP/1.0 的标准,现在更倾向于用 HTTP/1.1 中定义的 Cache-Control,两个同时存在时也是 Cache-Control 的优先级更高。

Cache-Control

为了解决这个问题,在 HTTP1.1 中增加了 Cache-Control 这个字段。

Cache-Control:max-age=7200 

服务器和客户端说,这个资源缓存只可以存在 7200 秒,在这个时间段之内,你就可以在缓存获取资源。

如果 Expire 和 Cache-control 两者同时出现,则以 Cache-control 为主

除此之外,cache-control 还有其他字段可以使用。

cache-control: max-age=3600, s-maxage=31536000 
  • Public:只要为资源设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存
  • Private(默认值):则该资源只能被浏览器缓存
  • no-store:不使用任何缓存,直接向服务器发起请求
  • no-cache:绕开浏览器缓存(每次发起请求不会询问浏览器缓存),而是直接向服务器确认该缓存是够过期

Cache-Control 可以由多个字段组合而成,主要有以下几个取值:

1. max-age:指定一个时间长度,在这个时间段内缓存是有效的,单位是s。例如设置 Cache-Control:max-age=31536000,也就是说缓存有效期为(31536000 / 24 / 60 * 60)天,第一次访问这个资源的时候,服务器端也返回了 Expires 字段,并且过期时间是一年后。

在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。

2. s-maxage:同 max-age,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略。

3. public:表明响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。

4. private:表明响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。

5. no-cache:强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器。不是字面意思上的不缓存。

6. no-store:禁止缓存,每次请求都要向服务器重新获取数据。

协商缓存


浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。

再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。

缓存的资源到期了,并不意味着资源内容发生了改变,如果和服务器上的资源没有差异,实际上没有必要再次请求。客户端和服务器端通过某种验证机制验证当前请求资源是否可以使用缓存。

浏览器第一次请求数据之后会将数据和响应头部的缓存标识存储起来。再次请求时会带上存储的头部字段,服务器端验证是否可用。如果返回 304 Not Modified,代表资源没有发生改变可以使用缓存的数据,获取新的过期时间。反之返回 200 就相当于重新请求了一遍资源并替换旧资源。

// 命中缓存的响应字段 
Request Method:GET 
Status Code: 304 Not Modified 

 

 

 

怎么来识别协商缓存的?


主要通过报文头部 header 中的Last-Modified,If-Modified-Since 以及ETag、If-None-Match 字段来进行识别。

(1) Last-Modified/If-Modified-Since(If-Modified-Since比较资源的更新时间)

Last-modified: 服务器端资源的最后修改时间,响应头部会带上这个标识。

第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since 即为之前记录下的时间。

服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。

Last-Modified 字段的意思是服务器资源的最后修改时间。第一次请求服务器,服务器的头部字段可增加这个字段,用于设置协商缓存。

Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT 

 当浏览器再次发起请求的时候,首部字段增加 If-Modified-Since 本地时间戳字段发给服务器

If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT 

服务端接收到请求之后,就拿 If-Modified-Since 字段值和本身的过期时间对比。

如果请求头中的这个值小于最后修改时间,返回的 304 响应,让其在本地浏览器缓存取出数据。如果时间过期,并在 Response Headers中添加新的 Last-Modified 值返回给浏览器。

但是 Last-Modified 存在一个局限性,有以下两种情况:

  • 不该请求,还会请求。编辑了文件,文件内容没有变,但是服务器确认为我们改动了文件,所以重新设置了缓存时间,当做新请求返回给浏览器。
  • 该请求,反而没有请求。修改文件速度很快,快过 If-Modified-Since 字段时间差的检测,文件虽然改动了,但是并没有重新生成新的资源。

(2) Etag/If-None-Match (ETag资源的匹配信息 If-Match比较实体标记(ETag) If-None-Match 比较实体标记 与 If-Match 相反)

ETag 代表的意思是标识字符串。由于上述 Last-Modified 字段存在的缺陷,所以在 HTTP / 1.1 我们对资源进行内容编码,只要内容被改变,这个编码就不同。

和上述请求原理一样,浏览器首次发起请求,然后服务器在响应头返回一个标识字符串

ETag: W/"2a3b-1602480f459" 

 浏览器再次发起请求,携带一个值相同的字符串。

If-None-Match: W/"2a3b-1602480f459" 

服务端接收到该字符串就会作对比,如果相同,则让其读取本地缓存,否则,将新的资源返回给浏览器端。

ETag由服务器端上生成的一段 hash 字符串,第一次请求时响应头带上 ETag: abcd,之后的请求中带上 If-None-Match: abcd,服务器检查 ETag,返回 304 或 200。

关于 last-modified 和 Etag 区别,已经有很多人总结过了:

  • 某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新。
  • Last-modified 只能精确到秒。
  • 一些资源的最后修改时间改变了,但是内容没改变,使用 Last-modified 看不出内容没有改变。
  • Etag 的精度比 Last-modified 高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供 ETag 的话,必须先对 ETag 进行 Conditional Request。

注意:实际使用 ETag/Last-modified 要注意保持一致性,做负载均衡和反向代理的话可能会出现不一致的情况。计算 ETag 也是需要占用资源的,如果修改不是过于频繁,看自己的需求用 Cache-Control 是否可以满足。

 选择 Cache-Control 的策略(摘自 Google Developers)

 

实际应用


回到实际应用上来,首先要明确哪些内容适合被缓存哪些不适合。

考虑缓存的内容:

  • css 样式文件
  • js 文件
  • logo、图标
  • html 文件
  • 可以下载的内容

一些不应该被缓存的内容:

  • 业务敏感的 GET 请求

可缓存的内容又分为几种不同的情况:

不经常改变的文件:给 max-age 设置一个较大的值,一般设置 max-age=31536000

比如引入的一些第三方文件、打包出来的带有 hash 后缀 css、js 文件。一般来说文件内容改变了,会更新版本号、hash 值,相当于请求另一个文件。

标准中规定 max-age 的值最大不超过一年,所以设成 max-age=31536000。至于过期内容,缓存区会将一段时间没有使用的文件删除掉。

可能经常需要变动的文件:

Cache-Control: no-cache / max-age=0

比如入口 index.html 文件、文件内容改变但名称不变的资源。选择 ETag 或 Last-Modified 来做验证,在使用缓存资源之前一定会去服务器端做验证,命中缓存时会比第一种情况慢一点点,毕竟还要发请求进行通信。

注意: 这里只描述了最基本的思路,实际使用 HTTP 缓存需要后端配合配置,具体情况具体对待,而且各方的实现并不一定完全按照标准来的,踩踩坑更健康?。

 

缓存位置


缓存的位置按照获取资源请求优先级,缓存位置依次如下:

  • Memory Cache(内存缓存)
  • Service Worker(离线缓存)
  • Disk Cache(磁盘缓存)
  • Push Cache(推送缓存)

(1) Memory Cache

Memory 为内存缓存,是浏览器最先尝试命中的缓存,也是响应最快的缓存。但是存活时间最短的,当进程结束后,tab 标签关闭后,缓存就不存在了。

因为内存空间比较小,通常较小的资源放在内存缓存中,比如 base64 图片等资源。

(2) Service Worker

Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。

可以帮我们实现离线缓存、消息推送和网络代理等功能。

(3) Disk Cache

内存的优先性,导致大文件不能缓存到内存中,那么磁盘缓存则不同。虽然存储效率比内存缓存慢,但是存储容量和存储市场有优势。

(4) Push Cache

它是最后一道缓存命中,属于 HTTP2 的内容。如果感兴趣的同学,可以先去了解了解。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值