# HTTP 缓存

HTTP 缓存对性能优化的重要性不言而喻,只要命中缓存就不需要重新加载一遍文件,js 执行时间相比下载时间要快的多,如果能优化下载时间,用户体验会提升很多

HTTP 缓存可以分为:

  1. 强缓存
  2. 协商缓存

# 强缓存

强缓存优先级较高,HTTP 会优先判断是否命中强缓存。那么 HTTP 是怎么判断的呢。

强缓存是利用 HTTP 头中的 ExpiresCache-Control 两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的 expirescache-control 判断目标资源是否“命中”强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通信

# Expires

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

expires 是一个时间戳 通过对比时间戳来决定是否命中强缓存 但 expires 是有问题的,它最大的问题在于对“本地时间”的依赖。如果服务端和客户端的时间设置可能不同,或者我直接手动去把客户端的时间改掉,那么 expires 将无法达到我们的预期。

# Cache-Control

 // Cache-Control
 Cache-Control:max-age=10

HTTP1.1 新增了 Cache-Control 在 Cache-Control 中,我们通过 max-age 来控制资源的有效期。max-age 不是一个时间戳,而是一个时间长度。

  • max-age:单位是秒,缓存时间计算的方式是距离发起的时间的秒数,超过间隔的秒数缓存失效
  • no-cache:不使用强缓存,需要与服务器验证缓存是否新鲜
  • no-store:顾名思义就是不使用任何缓存策略。每次访问资源,浏览器都必须请求服务器,并且,服务器不去检查文件是否变化,而是直接返回完整的资源。
  • private:专用于个人的缓存,中间代理、CDN 等不能缓存此响应
  • public:响应可以被中间代理、CDN 等缓存
  • must-revalidate:只要过期就必须回源服务器验证,
  • proxy-revalidate:只要求代理的缓存过期后必须验证,客户端不必回源,只验证到代理这个环节就行了
  • s-maxage :限定在代理上能够存多久
  • no-transform:代理有时候会对缓存下来的数据做一些优化,比如把图片生成 png、webp 等几种格式,方便今后的请求处理。 no-transform 就会禁止这样做

# Pragma

Pragma 只有一个属性值,就是 no-cache ,效果和 Cache-Control 中的 no-cache 一致,不使用强缓存,需要与服务器验证缓存是否新鲜,在 3 个头部属性中的优先级最高。

# 协商缓存

当浏览器的强缓存失效的时候或者请求头中设置了不走强缓存,并且在请求头中设置了 If-Modified-Since 或者 If-None-Match 的时候,会将这两个属性值到服务端去验证是否命中协商缓存,如果命中了协商缓存,会返回 304 状态,加载浏览器缓存,并且响应头会设置 Last-Modified 或者 ETag 属性。

# Last-Modified/If-Modified-Since

//client
If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT
//Server
Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT

即最后修改时间。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。

第二次发起请求的时候,请求头会带上上一次响应头中的 Last-Modified 的时间,并放到 If-Modified-Since 请求头属性中,服务端根据文件最后一次修改时间和 If-Modified-Since 的值进行比较,如果相等,返回 304 ,并加载浏览器缓存。

缺点:

  • 由于 Last-Modified 修改时间是 GMT 时间,只能精确到秒,如果文件在 1 秒内有多次改动,服务器并不知道文件有改动,浏览器拿不到最新的文件
  • 如果服务器上文件被多次修改了但是内容却没有发生改变,服务器需要再次重新返回文件。

# ETag

ETag 就是为了解决 Last-Modified 无法解决高频修改文件缓存问题,可以称他为 文件指纹

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

ETag 还有之分。

强 ETag 要求资源在字节级别必须完全相符,弱 ETag 在值前有个“W/”标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变(例如 HTML 里的标签顺序调整,或者多了几个空格)。

缺点:

  • 显而易见, ETag需要去检查文件字节变化,然后生成 hash 字符串,增加了运算成本。
//client
If-None-Match: W/"2a3b-1602480f459"
//Server
ETag: W/"2a3b-1602480f459"

Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。

# 整体流程

缓存顺序

当浏览器再次访问一个已经访问过的资源时,它会这样做:

  1. 看看是否命中强缓存,如果命中,就直接使用缓存了。(express 和 Cache-Control:max-age)
  2. 如果没有命中强缓存,就发请求到服务器检查是否命中协商缓存。(if-last-modify 和 Etag)
  3. 如果命中协商缓存,服务器会返回 304 告诉浏览器使用本地缓存。
  4. 否则,返回最新的资源。

# 缓存位置

当强缓存命中或者协商缓存中服务器返回 304 的时候,会从本地取,那么本地缓存在哪里呢

浏览器中的缓存位置有以下 4 种,按优先级分别是:

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

# Service Worker

Service Worker 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存消息推送网络代理等功能。其中的离线缓存就是 Service Worker CacheService Worker 同时也是 PWA 的重要实现机制

PS:大家注意 Server Worker 对协议是有要求的,必须以 https 协议为前提。

# Memory Cache 和 Disk Cache

Memory Cache 顾名思义,就是将资源缓存到内存中,等待下次访问时不需要重新下载资源,而直接从内存中获取。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

--- memory cache disk cache
相同点 只能存储一些派生类资源文件 只能存储一些派生类资源文件
不同点 退出进程时数据会被清除 退出进程时数据不会被清除
存储资源-scale 一般脚本、字体、图片会存在内存当中 般非脚本会存在内存当中,如 css 等
  • 比较大的 JS、CSS 文件会直接被丢进磁盘,反之丢进内存
  • 内存使用率比较高的时候,文件优先进入磁盘

# Push Cache

推送缓存 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。

# 用户操作对缓存的影响

# 地址栏输入地址

优先查找Disk Cache看是否有匹配,没有则发送网络请求

# F5 刷新

优先查找 Memory Cache 然后再去匹配 Disk Cache。不走强缓存但是

# CTRL+F5 刷新

不使用缓存

# 缓存场景

对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略

对于某些不需要缓存的资源,可以使用 Cache-control: no-store ,表示该资源不需要缓存 对于频繁变动的资源,可以使用 Cache-Control: no-cache 并配合 ETag 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新 对于代码文件来说,通常使用 Cache-Control: max-age=31536000 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件


# 参考资料

《前端性能相关》 (opens new window)

《图解 HTTP 缓存》 (opens new window)

《前端也要懂的 HTTP 缓存》 (opens new window)

《HTTP 缓存和浏览器的本地存储》 (opens new window)

Last Updated: 12/22/2022, 9:53:26 AM