我与缓存的相爱相杀

1,066 阅读9分钟

缓存是我们这些切图仔绕不开的话题,但是你不理解它就会出现需要时,拿不到,不需要时,总是出来作怪,今天这篇文章将带梳理下于缓存有关的知识。

那么什么是缓存呢?

缓存是浏览器的一种机制,叫浏览器缓存机制,该机制可以把一个请求过的 Web 资源(如 html 页面,图片,js,api数据等)拷贝一份到副本存储在浏览器中,并根据请求配置选择是否使用它们。

为什么使用缓存?

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

浏览器端缓存规则:对于浏览器的缓存来讲,这些规则是在 HTTP 协议头和 HTML 页面的 Meta 标签中定义的。

浏览器的缓存控制

① 使用 HTML Meta标签

我们可以在 HTML 页面的节点中加入标签

  <!- Pragma 是 http1.0 版本中给客户端设定缓存方式之一 ->
  <meta http-equiv="Pragma" content="no-cache">

上述代码含义是:浏览器当前页面不被缓存,每次访问都向服务器请求,但是通过这种方式去禁用缓存的形式用处很有限

  • 仅有 IE 才能识别这段 meta 标签含义,其它主流浏览器仅识别 "Cache-Control: no-store" 的mete标签
  • 在 IE 识别到该meta标签含义,并不一定会在请求字段加上 Pragma,但的确会让当前页面每次都发新请求(仅限页面,页面上的资源不受影响)

② 使用缓存有关的 HTTP 消息报头,这个后面会详细说

与缓存有关的消息报头有expires,cache-control,pragma,Last-Modified,If-modified-since,Etag,If-none-match 等。

浏览器的缓存机制可分为四个方面

  • Memory Cache
  • Service Worker Cache
  • HTTP Cache
  • Push Cache

这里我们只研究 HTTP Cache

为什么 HTTP 缓存可以提升效率呢?

我们知道,请求是个复杂的过程,需要经过

DNS解析 -> 因特网的五层协议 -> 服务器 -> 服务器又要进行安全校验 -> 根据请求执行相应的代码 -> 封装成 HTTP 请求 -> 四次挥手

最后我们的浏览器才拿到数据,而缓存就是把这一系列繁琐的过程都省略掉,当下一个请求来到时,如果是相同的 URL,直接读取相应的数据。

如果你还不理解,这里举个例子,商品的总部在北京,咦,它发现广东这边的市场对这个产品的需求量很大,那么它就在广州建一个仓库,那么广东这边购买该产品时,直接从广州发货,大大节省了时间,提升了效率.

好了,经过上面的解释,我们理解了,缓存是一种浏览器的机制,它会拷贝一份副本数据存储在本地,当遇到相同的请求,直接从本地读取数据,通过重复利用之前获取资源的方式来减少IO消耗,从而提高了访问速度,所以说,缓存是性能优化的一种手段。

既然缓存可以提高效率,那要怎么用呢? 不可能毫无章法吧,没错,确实有一套缓存规则,当输入 url,会进行一次数据请求,返回 http 响应头,就会携带相应的缓存规则,因此,要缓存规则可以由服务器决定。

缓存规则

HTTP缓存有许多规则,根据是否需要重新向服务器发起请求来分类,我将其分为两大类(强缓存和协商缓存)

顾名思义,强缓存意味着强制使用缓存,协商缓存意味着每用一次缓存都要协商一次。 强缓存和协商缓存都允许使用情况下,优先强缓存。

强缓存

控制字段:

  • Expires: HTTP1.0
  • Cache-Control: HTTP1.1 判断过程:请求再次发起 -> 浏览器根据 expires 和 cache-control 判断目标资源是否命中"强缓存" -> 若命中,直接从缓存获取资源,不再与服务器发生通讯。

Expires

响应头中的expires

expires: Sat,30 Mar 2019 10:31:59 GMT

流程

  • 首次请求
  • 服务器告知启用强缓存,并在响应头中带上 expires,告知缓存到期时间,该值是个时间戳。
  • 随后的每次请求,浏览器会先对比本地时间和 expires 的时间戳。
  • 如果本地时间小于 expires 设定的过期时间,那么就直接去缓存中取这个资源。

弊端: 依赖本地时间,如果用户修改了本地时间,那么 expires 就无法达到我们的预期。

基于对本地时间依赖这个弊端,HTTP1.1提出了 Cache-Control 来完全替代 expires 的任务,当两者并存情况下,优先 Cache-Control,在当下的前端实践里,继续使用 expires 的唯一目的就是向下兼容

Cache-Control

响应头中的 Cache-Control

cache-control: max-age=655350000

cache-control 中常用的值

max-age:指定的是从文档被访问后的存活时间,这个时间是个相对值,相对的是文档第一次被请求时服务器记录的。

Request_time(请求时间),也就是,相对的是文档的请求时间(Atime),单位:s。

s-maxage: 表示向代理服务器请求缓存内容,只在代理服务器中生效,优先级高于 max-age,客户端中只考虑 max-age。

pubilc 与 private: 针对资源是否能够被代理服务器缓存而存在的一组对立概念,如果设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存,如果我们设置了 private,则只能被浏览器缓存,默认值是 private。 no-cache: 每一次发起请求都不会再去询问浏览器缓存情况,而是直接向服务器去确认资源是否过期(等于直接使用协商缓存)。 no-store: 不使用任何缓存策略,直接向服务端发送请求,并下载完整的响应。

已存在缓存数据时,仅基于强制缓存,请求数据流程如下:

强缓存命中 与未命中

强缓存生效的网络请求图

强缓存生效流程图
状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存存放的位置,分别为from memory cache 和 from disk cache。

协商缓存

控制字段: (HTTP1.0)

  • Last-Modified:是一个时间戳,由服务器生成,如果规则中启用了协商缓存,它会在首次请求时,随着 Response Headers 返回,存在响应头中。
  • If-Modified-Since:也是一个时间戳,依托着 Last-Modified,是它的复制品,由浏览器生成,在启用协商缓存情况下,每次的请求头中都会带上这个字段,它的值等于上一次响应头返回的 last-modified 值。 (HTTP1.1)
  • Etag:服务器为每个资源生成的唯一的标识字符串,这个标识字符串时基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的,由服务器产生,存在响应头中。
  • If-None-Match: Etag 的复制品,用法和 If-Modified-Since 一样,存在请求头中。

如果服务器提示资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下,网络请求对应的状态码是304。

304状态码请求图

Last-Modified 与 If-Modified-Since

响应头中的 Last-Modified

last-modified: Wed,19 Dec 2018 08:57:33 GMT 请求头中的 If-Modified-Since If-Modified-Since: Mon,14 Jan 2019 03:31:14 GMT

流程

  • 首次请求
  • 服务器告知启用协商缓存规则,并在响应头中带上 Last-Modified,告知缓存到期时间
  • 随后的每次请求,请求头上都会携带 If-Modified-Since,该值等于上一次响应头中的 Last-Modified 的值
  • 服务器收到 If-Modified-Since 后,会将该属性的值与服务器上资源的最后修改时间进行匹配,从而判断资源是否发生了变化
  • 如果发生变化会返回一个完整的响应内容,在响应头中添加新的 Last-Modified 值,否则,只返回header部分,状态码为304,响应头不会再添加 Last-Modified.

弊端: Last-Modified无法正确感知文件的变化,譬如说,文件的编辑时间修改了而内容没有修改,或者修改文件速度太快,几毫秒就改一次文件,If-Modified-Since 只能检测秒级的变化.

为了解决这个问题,Etag 作为 Last-Modified 的升级版,因时而生

Etag 是通过标识字符串来辨别文件内容是否发生修改的,文件内容不一致才会生成新的标识字符串,这就弥补了 Last-Modified 时间戳的不足,通过 Etag 可以精准的感知文件的变化.

Etag 与 If-None-Match

响应头中的 Etag

etag: "2c6bee6a6ab9e39b892970e9368a3dff" 请求头中的 If-None-Match If-None-Match: "2c6bee6a6ab9e39b892970e9368a3dff"

流程

  • 首次请求
  • 服务器启用协商缓存情况下,会在响应头中带上 Etag
  • 随后每次请求,请求头上都会带上 If-None-Match,该值等于上一次响应头中的 Etag 的值
  • 服务器收到 If-None-Match 后,会进行比对,从而判断资源是否发生变化
  • 如果变化返回一个完整响应内容,在响应头上添加新的 Etag 值,否则返回 304,响应头不会在添加 Etag

弊端: Etag的生成需要服务器付出额外的开销,会影响服务端性能

Etag 并不能替代 Last-Modified,只能作为 Last-Modified 的补充和强化存在,当 Etag 和 Last-Modified 同时出现时,以 Etag 为准

已存在缓存数据时,仅基于协商缓存,请求数据的流程如下

协商缓存命中 与未命中

协商缓存生效的网络请求图

协商缓存生效流程图

不能缓存的请求

  • HTTP信息头中包含Cache-Control:no-cache,pragma:no-cache(HTTP1.0),或Cache-Control:max-age=0等告诉浏览器不用缓存的请求
  • 需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
  • 经过HTTPS安全加密的请求
  • POST请求无法被缓存
  • HTTP响应头中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的请求无法被缓存