浅析HTTP缓存
本文最后更新于:2020年2月26日 早上
浏览器缓存机制目前有四个方面,按照资源获取优先级排列,分别是 Memory Cache 、 Service Worker Cache、HTTP Cache、Push Cache。我们比较熟悉也经常使用的是 HTTP Cache。这里单独分析 HTTP Cache。
HTTP Cache
HTTP Cache 又分为强缓存和协商缓存,强缓存优先级比较高,未命中强缓存时才走协商缓存。
强缓存
强缓存是通过http头的 expires 和 cache-control 来实现控制。当再发出请求时,浏览器会根据 expires 和 cache-control 来判断是否命中强缓存,若命中直接从缓存中获取资源,而不再与服务端进行通信。
在 http1.0 版本中,服务器响应时会返回一个缓存过期时间戳字段 expires,再次发起请求时浏览器通过本地时间与 expires 时间戳对比,来判断缓存是否过期,这种依赖本地时间的方式会存在一个弊端,如果修改了本地时间,或者本地时间与服务端时间不同步,这将达不到我们预期的缓存效果。
因此,在 http1.1 版本中,引入了 expires 的替代方案:Cache-Control。Cache-Control 能做 expires 做的事,也能做 expires 做不到的事情,目前 expires 的存在是为了做向下兼容。
Cache-Control 的格式为:
| 1 |  | 
在Cache-Control中,max-age字段表示资源最大缓存时间(秒),上面例子表示在3600秒内该资源都是有效的。Cache-Control表示的时间比expires时间戳更为准确,同时Cache-Control优先级更高,两者存在时以Cache-Control为准。
Cache-Control还有其它的缓存方式:no-cache和no-store。
如果给资源设置了no-cache,则会绕开浏览器,直接询问服务器该资源是否过期,即走协商缓存。no-store顾名思义,就比较绝情了,不设置任何缓存,每次发起请求不经过浏览器与服务端缓存,直接想服务器发送请求,并下载完整的响应。
协商缓存
协商缓存,顾名思义就是浏览器与服务器合作的缓存策略,它依赖于浏览器与服务器之间的通信,浏览器需要向服务器询问缓存的相关信息,进而判断是读取本地缓存的资源还是重新发起请求,下载完整的响应。
协商缓存的实现
Last-Modified
Last-Modified是一个时间戳,在启用协商缓存之后,会在首次请求的时候,随着Response Headers返回
| 1 |  | 
随后每次请求会带上If-Modified-Since时间戳字段,值为上一次Response返回的Last-Modified值
| 1 |  | 
服务器收到这个时间戳之后,会根据该时间戳与服务器的资源的最后修改时间对比,如果时间改变,则返回一个新的完整的响应,并在 Response Headers 返回新的 Last-Modified 值;如果时间没有变化,则返回一个 304 状态码,提示资源并未改动,然后重定向到浏览器缓存,Response Headers 也不会再返回 Last-Modified 字段,如下图:

但是使用 Last-Modified 也会存在弊端:
- 资源文件改动了,但是并没有改变内容,文件的最后修改时间会变化,导致该资源会被重新请求
- If-Modified-Since是以秒为单位做检验,如果改动文件的时间小于1s,那么该资源有可能被认为是未改变的,导致无法重新请求
这两种情况反映了一个问题,无法准确感知文件是否发生改变。
为了解决该问题,Etag出现了。
Etag是服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,文件内容不同,对应的 Etag 就是不同的,因此 Etag  能够精准地感知文件的变化。
Etag 和 Last-Modified 类似,当首次请求时,我们会在响应头里获取到一个最初的标识符字符串,它可以是这个样子
| 1 |  | 
在下一次请求时,会带上与之相同值的字段if-None-Match,提供给服务器对比
| 1 |  | 
很明显,Etag 有利也有弊,它的生成需要服务器付出额外的开销,会影响服务器的性能,Etag 并不能直接代替Last-Modified,而是作为补充,它的优先级比 Last-Modified 高,两者共存时,以 Etag 为准。
HTTP缓存策略
对于上面讲的知识点,要如何对应到实际的开发中呢,这边拿了谷歌官方的一张图:

这张图片清楚地给我们展示了缓存策略流程。
首先看看资源的可复用性,如果是不可复用的话,很干脆,直接把 Cache-control 设置为 no-store ,不需要任何形式的缓存。
如果是可复用的资源,那么看是否需要每次都向服务器去验证缓存是否有效,如果需要,则设置 Cache-control 为 no-cache ;不需要的,则进行下一步,考虑资源是否可被代理服务器缓存,根据情况设置 private 或 public ;接着考虑资源的过期时间,设置资源的 max-age ;最后一步配置协商缓存,设置 Last-Modified 和 Etag。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!