浅析HTTP缓存

本文最后更新于:2020年2月26日 早上

浏览器缓存机制目前有四个方面,按照资源获取优先级排列,分别是 Memory CacheService Worker CacheHTTP CachePush Cache。我们比较熟悉也经常使用的是 HTTP Cache。这里单独分析 HTTP Cache

HTTP Cache

HTTP Cache 又分为强缓存协商缓存,强缓存优先级比较高,未命中强缓存时才走协商缓存。

强缓存

强缓存是通过http头的 expirescache-control 来实现控制。当再发出请求时,浏览器会根据 expirescache-control 来判断是否命中强缓存,若命中直接从缓存中获取资源,而不再与服务端进行通信。

在 http1.0 版本中,服务器响应时会返回一个缓存过期时间戳字段 expires,再次发起请求时浏览器通过本地时间与 expires 时间戳对比,来判断缓存是否过期,这种依赖本地时间的方式会存在一个弊端,如果修改了本地时间,或者本地时间与服务端时间不同步,这将达不到我们预期的缓存效果。

因此,在 http1.1 版本中,引入了 expires 的替代方案:Cache-ControlCache-Control 能做 expires 做的事,也能做 expires 做不到的事情,目前 expires 的存在是为了做向下兼容。

Cache-Control 的格式为:

1
Cache-Control: max-age=3600

Cache-Control中,max-age字段表示资源最大缓存时间(秒),上面例子表示在3600秒内该资源都是有效的。Cache-Control表示的时间比expires时间戳更为准确,同时Cache-Control优先级更高,两者存在时以Cache-Control为准。

Cache-Control还有其它的缓存方式:no-cacheno-store

如果给资源设置了no-cache,则会绕开浏览器,直接询问服务器该资源是否过期,即走协商缓存no-store顾名思义,就比较绝情了,不设置任何缓存,每次发起请求不经过浏览器与服务端缓存,直接想服务器发送请求,并下载完整的响应。

协商缓存

协商缓存,顾名思义就是浏览器与服务器合作的缓存策略,它依赖于浏览器与服务器之间的通信,浏览器需要向服务器询问缓存的相关信息,进而判断是读取本地缓存的资源还是重新发起请求,下载完整的响应。

协商缓存的实现

Last-Modified

Last-Modified是一个时间戳,在启用协商缓存之后,会在首次请求的时候,随着Response Headers返回

1
Last-Modified: Wed, 12 Feb 2020 09:21:28 GMT

随后每次请求会带上If-Modified-Since时间戳字段,值为上一次Response返回的Last-Modified

1
If-Modified-Since: Wed, 12 Feb 2020 09:21:28 GMT

服务器收到这个时间戳之后,会根据该时间戳与服务器的资源的最后修改时间对比,如果时间改变,则返回一个新的完整的响应,并在 Response Headers 返回新的 Last-Modified 值;如果时间没有变化,则返回一个 304 状态码,提示资源并未改动,然后重定向到浏览器缓存,Response Headers 也不会再返回 Last-Modified 字段,如下图:

304

但是使用 Last-Modified 也会存在弊端:

  • 资源文件改动了,但是并没有改变内容,文件的最后修改时间会变化,导致该资源会被重新请求
  • If-Modified-Since是以为单位做检验,如果改动文件的时间小于1s,那么该资源有可能被认为是未改变的,导致无法重新请求

这两种情况反映了一个问题,无法准确感知文件是否发生改变。

为了解决该问题,Etag出现了。

Etag是服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,文件内容不同,对应的 Etag 就是不同的,因此 Etag 能够精准地感知文件的变化。

EtagLast-Modified 类似,当首次请求时,我们会在响应头里获取到一个最初的标识符字符串,它可以是这个样子

1
ETag: W/"1q2w-108946715"

在下一次请求时,会带上与之相同值的字段if-None-Match,提供给服务器对比

1
If-None-Match: W/"1q2w-108946715"

很明显,Etag 有利也有弊,它的生成需要服务器付出额外的开销,会影响服务器的性能,Etag 并不能直接代替Last-Modified,而是作为补充,它的优先级比 Last-Modified 高,两者共存时,以 Etag 为准。

HTTP缓存策略

对于上面讲的知识点,要如何对应到实际的开发中呢,这边拿了谷歌官方的一张图:

缓存策略

这张图片清楚地给我们展示了缓存策略流程。

首先看看资源的可复用性,如果是不可复用的话,很干脆,直接把 Cache-control 设置为 no-store ,不需要任何形式的缓存。

如果是可复用的资源,那么看是否需要每次都向服务器去验证缓存是否有效,如果需要,则设置 Cache-controlno-cache ;不需要的,则进行下一步,考虑资源是否可被代理服务器缓存,根据情况设置 privatepublic ;接着考虑资源的过期时间,设置资源的 max-age ;最后一步配置协商缓存,设置 Last-ModifiedEtag


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!