Skip to content
当前页面

浏览器的缓存机制

浏览器缓存机制图解

浏览器缓存主要有两类:强缓存和协商缓存。

强缓存

不用请求服务器,直接使用本地缓存,利用 http 响应头中的ExpriresCache-Cantrol实现。

Expires 或者 Cache-Control 两个字段表示资源的缓存时间。

Expires 是 http1.0 时的规范,是服务器返回一个的绝对时间(GMT 格式的时间字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT),这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。所以,在 http1.1 中,提出了一个新的 response header,就是 Cache-Control。

Cache-Control主要是利用该字段的 max-age 值来进行判断,它是一个相对时间,例如 Cache-Control:max-age=3600,代表着资源的有效期是 3600 秒。cache-control 除了该字段外,还有下面几个比较常用的设置值:

  • no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在 ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
  • no-store:直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
  • public:可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。
  • private:只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存

协商缓存

浏览器发现本地有资源的副本,但是不太明确要不要使用,于是去问问服务器。

协商缓存是利用的是两对 Header:

  • 第一对:Last-Modified、If-Modified-Since
  • 第二对:ETag、If-None-Match

这两组搭档都是成对出现的,即第一次请求的响应头带上某个字段(Last-Modified 或者 Etag),则后续请求则会带上对应的请求字段(If-Modified-Since 或者 If-None-Match),若响应头没有 Last-Modified 或者 Etag 字段,则请求头也不会有对应的字段。

Last-Modify/If-Modify-Since

  1. 浏览器第一次请求一个资源的时候,服务器返回的 response header 中会加上 Last-Modify,标识该资源的最后修改时间,例如 Last-Modify: Thu,31 Dec 2037 23:59:59 GMT。
  2. 当浏览器再次请求该资源时,request header 中会包含 If-Modify-Since,该值为缓存之前返回的 Last-Modify。
  3. 服务器收到第二次请求时的 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。
  4. 如果命中缓存,则返回 304,并且不会返回资源内容,并且不会返回 Last-Modify。

注意:

Last-Modified、If-Modified-Since 一般来说都是非常可靠的,但有可能出现的问题是:服务器上的资源变化了,但是最后的修改时间却没有变化。这一对 header 就无法解决这种情况。于是,ETag/If-None-Match 就诞生了。

ETag/If-None-Match

  1. ETag 是一个资源校验码,它可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化,跟最后修改时间无关,所以也就很好地补充了 Last-Modified 的不足。
  2. 浏览器第一次请求一个资源,服务器在返回这个资源的同时,会加上 ETag 这个 response header,这个 header 是服务器根据当前请求的资源生成的唯一标识。
  3. 浏览器再次请求这个资源时,会加上 If-None-Match 这个 request header,这个 header 的值就是上一次返回的 ETag 的值。
  4. 服务器第二次请求时,会对比浏览器传过来的 If-None-Match 和服务器重新生成的一个新的 ETag,判断资源是否有变化。如果没有变化则返回 304 Not Modified,但不返回资源内容(此时,由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 并无变化)。如果有变化,就正常返回资源内容(继续重复整个流程)。
  5. 浏览器如果收到 304 的响应,就会从缓存中加载资源。

提示:

Last-Modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。

缓存类型获取资源形式状态码发送请求到服务器
强缓存从缓存取200(from cache)否,直接从缓存取
协商缓存从缓存取304(Not Modified)否,通过服务器来告知缓存是否可用

用户行为对缓存的影响

用户操作Expires/Cache-Control(强缓存)Last-Modify/ ETag(协商缓存)
地址栏回车有效有效
页面链接跳转有效有效
新开窗口有效有效
前进回退有效有效
F5 刷新无效有效
Ctrl+F5 强刷新无效无效

如何控制缓存

一种方法是在所有静态资源文件后面添加随机时间戳,例如:

html
<script
  type="text/javascript"
  src="https://www.demo.com/demo.js?v=1234"
></script>

每次修改 demo.js 之后修改 v 后面的时间戳,这样浏览器就会忽略缓存从服务器请求新的文件。 但是,由于 html 页面没有加时间戳,它页面依然是使用的缓存,html 又是所有静态资源的载体,所以其他资源也是依然使用的缓存。

还有一种方法是在 meta 标签上添加 cache-control,如下:

html
<!--设置过期时间设置0为直接过期并清除缓存-->
<meta http-equiv="Expires" content="0" />
<!--设置不缓存页面-->
<meta http-equiv="Pragma" content="no-cache" />
<!--设置不修改消息存储-->
<meta http-equiv="Cache-control" content="no-cache" />
<!--同上-->
<meta http-equiv="Cache" content="no-cache" />

但是,这样做并没有什么用,完全没办法避免浏览器的缓存,添加 cache-control 没错,但是需要在响应头添加,所以我们需要修改服务器配置。以下是 nginx 配置的参考:

nginx
#   利用正则表达式匹配静态资源目录
location ~ /dest/(.*) {
    root   E:/Workspace/my;
    add_header Cache-Control no-cache;
    add_header Cache-Control private;
    expires 0s;
}

如此配置后,dest 目录下的所有文件将不再缓存,每次都从服务器端请求了。