# 浏览器缓存

浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:

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

浏览器缓存

其中,“from memory cache”对标到 Memory Cache 类型,“from ServiceWorker”对标到 Service Worker Cache 类型。至于 Push Cache,这个比较特殊,是 HTTP2 的新特性。

# HTTP缓存

它需要服务端和客户端配合才能完成,常见的服务器软件如:Apache、Nginx都可以为资源设置不同的HTTP缓存策略。HTTP缓存策略又细分为 强制缓存协商缓存 。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。

# 强制缓存

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

命中强缓存的情况下,返回的 HTTP 状态码为 200 (如下图):

HTTP 状态码 200

# Expires

expires是强制缓存策略的关键字段,expires是HTTP1.0的字段,是一个绝对时间值。当服务器返回响应时,在 Response Headers 中将过期时间写入 expires 字段。像这样:

Expires

expires 是一个时间戳,当再次向服务器请求资源,浏览器本地时间和 expires 的时间戳会进行对比,如果本地时间小于 expires 设定的过期时间,那么就直接去缓存中取这个资源。

expires 最大的问题在于对“本地时间”的依赖。如果服务端和客户端的时间设置可能不同,或者我直接手动去把客户端的时间改掉,那么 expires 将无法达到我们的预期。可以通过 Cache-Control 解决这个问题。

# Cache-Control

HTTP1.1 新增了 Cache-Control 字段来完成 expires 的任务。Cache-Control 可以视作是 expires 的完全替代方案。Cache-Control设置的是一个相对时间,可以更加精准地控制资源缓存。

Cache-Control

# 可缓存性

  • public 设置了该字段值的资源表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是使用**max-age=**来精确控制;
  • private 设置了该字段值的资源只能被用户浏览器缓存,不能作为共享缓存,不允许任何代理服务器缓存。在实际开发当中,对于一些含有用户信息的HTML,通常都要设置这个字段值,避免代理服务器(CDN)缓存;
  • no-cache 设置了该字段需要先和服务端确认返回的资源是否发生了变化,如果资源未发生变化,则直接使用缓存好的资源;
  • no-store 设置了该字段表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源;

# 到期时间

  • max-age= 设置缓存的最大有效期,单位为秒;
  • s-maxage=: 优先级高于max-age=,仅适用于共享缓存(CDN),优先级高于max-age或者Expires头;
  • max-stale[=]: 设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。

实际开发当中expires和Cache-Control一般都要进行设置,这是为了兼容不支持HTTP1.1的环境。两者同时存在,Cache-Control的优先级要高于expires。

# 协商缓存

没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了

# Last-Modified/If-Modified-Since

Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers 返回:

Last-Modified: Fri, 27 Oct 2019 06:35:57 GMT

随后我们每次请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:

If-Modified-Since: Fri, 27 Oct 2019 06:35:57 GMT

服务器接收到这个时间戳后,根据 If-Modified-Since 的字段值和服务端资源最后的修改时间是否一致来判断资源是否有修改。如果没有修改,则返回的状态码为304;如果有修改,则返回新的资源,状态码为200。

# Last-Modified 弊端

  • 服务端对Last-Modified标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内被修改多次的话,这个时候服务端无法准确标注文件的修改时间。
  • 服务端文件进行了编辑,但文件的内容没有改变,这个资源在再次被请求时,会被当做新资源,导致文件无法使用缓存。

# Etag/If-None-Match

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

EtagLast-Modified 类似,当首次请求时,服务器响应头里返回一个最初的标识符字符串:

ETag: W/"2a3b-1602480f459"

随后我们每次请求时,会带上一个叫 if-None-Match 的字符串,它的值正是上一次 response 返回给它的 Etag 值:

If-None-Match: W/"2a3b-1602480f459"

# Etag 弊端

Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。Etag 并不能替代 Last-Modified,它只能作为 Last-Modified 的补充和强化存在。 Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。

# HTTP 缓存决策指南

HTTP 缓存决策指南

当资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数。

# 缓存字段的配置

# expires和cache-control

如果使用的是 Nginx,那么打开Nginx的配置文件 nginx.conf,具体配置方法如下:

// expires:给图片设置过期时间30天,这里也可以设置其它类型文件
location ~ \.(gif|jpg|jpeg|png)$ {
        root /var/www/img/;
        expires 30d;
}
// cache-control:给图片设置过期时间36秒,这里也可以设置其它类型文件
location ~ \.(gif|jpg|jpeg|png)$ {
        root /var/www/img/;
        add_header    Cache-Control  max-age=3600;
}

如果使用的是 Apache,那么打开Apache的配置文件 http.conf,具体的配置方法如下:

// expires
<IfModule expires_module>
      #打开缓存
      ExpiresActive on 
      # 给图片设置过期时间30天,这里也可以设置其它类型文件
      ExpiresByType image/gif access plus 30 days
      ExpiresByType image/jpg access plus 30 days
      ExpiresByType image/jpeg access plus 30 days
      ExpiresByType image/png access plus 30 days
</IfModule>
// cache-control
<FilesMatch "\.(gif/jpg/jpeg/png)$">
   	 Header set Cache-Control "max-age=604800, public"
</FilesMatch>

# Last-Modified和Etag

Last-Modified在Nginx和Apache当中都是默认启用的。

如果我们使用的是 Apache,那么只需要在 .htaccess 文件当中添加如下配置,即可开启Etag:

FileETag MTime Size 

如果我们使用的是 Nginx,需要先安装 Etag 模块;安装成功之后,仍然打开 nginx.conf 文件,确保当中没有出现 etgoff,然后添加如下配置,即可开启Etag:

location ~ .*\.(gif|jpg|jpeg|png)$ {
   FileETag on;
   etag_format "%X%X%X"; //这里格式化规则可以修改
 }

# Service Worker Cache

Service Worker是运行在浏览器背后的独立线程。可能大家在看到Service Worker这个概念的时候发现它经常和PWA一起出现。可以这么说,PWA应用在开发的时候必会用到Service Worker,这得益于Service强大的缓存能力。使用Service Worker前提必须是HTTPS协议,因为Service Worker涉及到了HTTP的拦截,所以必须使用HTTPS协议来保证安全。使用 Service Workers

# Memory Cache

Memory Cache指得是内存中的缓存,大家都知道内存的读取是非常快的,但是空间确有限,而且持续性很短,一旦我们关闭当前tab页面,Memory Cache也就随着消失了。Memory Cache缓存的大部分是preloader指令下的静态资源。我们发现,Base64 格式的图片,几乎永远可以被塞进 memory cache。

# Disk Cache

Disk Cache是存储在硬盘上的缓存,我们为静态资源设置的缓存一般都是缓存到Disk Cache当中,所以我们平时接触到最多的缓存就是Disk Cache。

# Push Cache

  • Push Cache是推送缓存,它是HTTP2.0新增加的内容,目前实际的应用还偏少。
  • Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。
  • Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。
  • 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。
  • 了解更多

# 参考

更新时间: 5/13/2020, 11:23:00 PM