HTTP缓存
灵感胜于汗水 Lv5

强制缓存

对于强制缓存而言,如果浏览器判断所请求的目标资源有效命中,则可直接从强制缓存中返回请求响应,无需与服务器进行任何通信。

Expires

服务器返回资源时在响应头中设置Expires字段,指定过期时间。在过期时间之前,浏览器再次访问该资源,则直接从强制缓存中获取。

1
Expires:New Date('2022-03-27 15:08:38').toUTCString()

缺点

这个方式存在一个很大的漏洞,即对本地时间戳过分依赖,如果客户端本地的时间与服务器端的时间不同步,或者对客户端时间进行主动修改,那么对于缓存过期的判断可能就无法和预期相符。

cache-control

Cache-Control 作为响应头,用以控制缓存策略,这也是前端 HTTP 缓存策略的基础。

max-age

cache-control设置max-age=60属性值来控制响应资源的有效期,它是一个以秒为单位的时间长度,表示该资源在被请求到后的60秒内有效,如此便可避免服务器端和客户端时间戳不同步而造成的问题。

1
cache-control:max-age=60//滑动时间,单位为秒

cache-control还可以配置一些其他属性值,下面介绍。

no-store和no-cache

no-store和no-cache是一组互斥属性

no-store:表示禁止使用缓存

no-cache:表示强制进行协商缓存

public和private

public和private是一组互斥属性

public:表示响应资源既可以被浏览器缓存,又可以被代理服务器缓存。

private:响应资源只能被浏览器缓存,若未显式指定则默认值为private。

对于应用程序中不会改变的文件,你通常可以在发送响应头前添加积极缓存(public)。这包括例如由应用程序提供的静态文件,例如图像,CSS 文件和JavaScript文件。

s-maxage

s-maxage:表示代理服务器缓存的过期时间。

协商缓存

协商缓存就是在使用本地缓存之前,需要向服务器端发起一次GET请求,与之协商当前浏览器保存的本地缓存是否已经过期。

last-modified/if-modified-since

响应头中设置:

1
2
cache-control:no-cache//进行协商缓存
last-modified:xxx//资源最后一次修改的时间

浏览器再次访问时请求头中会加上if-modified-since字段:

1
if-modified-since:xxx//值为上次请求时响应头的last-modified的值

服务器判断if-modified-since和此次的last-modified是否一致。如果一致则只需返回状态码304,表示从缓存中获取数据计可。如果不一致,就需要返回新的数据和last-modified。

缺陷

首先,它只是根据资源最后的修改时间戳进行判断的,虽然请求的文件资源进行了编辑,但内容并没有发生任何变化,时间戳也会更新,从而导致协商缓存时关于有效性的判断验证为失效,需要重新进行完整的资源请求。这无疑会造成网络带宽资源的浪费,以及延长用户获取到目标资源的时间。

其次,标识文件资源修改的时间戳单位是秒,如果文件修改的速度非常快,假设在几百毫秒内完成,那么上述通过时间戳的方式来验证缓存的有效性,是无法识别出该次文件资源的更新的。

etag和if-none-match

其内容主要是服务器为不同资源进行哈希运算所生成的一个字符串,只要文件内容编码存在差异,对应的 etag标签值就会不同,因此可以使用etag对文件资源进行更精准的变化感知。

响应头中设置:

1
etag:xxx//根据资源进行哈希运算所生成的一个字符串

浏览器再次访问时请求头中会加上if-none-match字段:

1
if-none-match:xxx//值为上次请求时响应头的etag的值

服务器判断if-none-match和此次的etag是否一致。如果一致则只需返回状态码304,表示从缓存中获取数据计可。如果不一致,就需要返回新的数据和etag。

缺陷

一方面,服务器对于生成文件资源的etag需要付出额外的计算开销,如果资源的尺寸较大,数量较多且修改比较频繁,那么生成etag的过程就会影响服务器的性能。

另一方面,etag字段值的生成分为强验证和弱验证,强验证根据资源内容进行生成,能够保证每个字节都相同;弱验证则根据资源的部分属性值来生成,生成速度快但无法确保每个字节都相同,并且在服务器集群场景下,也会因为不够准确而降低协商缓存有效性验证的成功率,所以恰当的方式是根据具体的资源使用场景选择恰当的缓存校验方式。

cache-control请求头

Cache-Control 也可以作为请求头,以最常见的 no-cachemax-age=0 为例,二者均会重新向服务器发起请求,哪怕该请求已被强缓存

  • Cache-Control: no-cache 作为请求头,表示即便在客户端拥有未过期的缓存,也要向服务器请求获得最新的资源。
  • Cache-Control: max-age=0 作为请求头,将会验证服务器资源的新鲜度,如果缓存未过期,则利用缓存,返回 304 状态码,否则重新获取资源返回 200 状态码。

结论:

  1. 只有服务端才能开启缓存,默认是不会走缓存的

  2. 走了强缓存就不会再向服务端发送请求了

  3. 客户端的请求头中只有设置了cache-control为:’no-store’ | ‘no-cache’ | ‘max-age=0’才会生效(也就是客户端不想走强缓存的时候生效),除非后端对这个字段做特殊处理

最佳实践

关于 http 缓存配置的最佳实践为以下两条:

  1. 文件路径中带有 hash 值:一年的强缓存。因为该文件的内容发生变化时,会生成一个带有新的 hash 值的 URL。前端将会发起一个新的 URL 的请求。配置响应头 Cache-Control: public,max-age=31536000,immutable
  2. 文件路径中不带有 hash 值:协商缓存。大部分为 public 下文件。配置响应头 Cache-Control: no-cacheetag/last-modified

但是当处理永久缓存时,切记不可打包为一个大的 bundle.js,此时一行业务代码的改变,将导致整个项目的永久缓存失效,此时需要按代码更新频率分为多个 chunk 进行打包,可细粒度控制缓存。

参考视频

  • 本文标题:HTTP缓存
  • 本文作者:灵感胜于汗水
  • 创建时间:2022-03-27 15:08:38
  • 本文链接:https://cjhsyc.github.io/2022/03/27/HTTP缓存/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!