浏览器缓存

缓存分类

  • 共享缓存:存储的响应能够被多个用户使用。
  • 私有缓存:只能用于单独用户。

缓存方式

  • 浏览器与代理缓存
  • 网关缓存
  • CDN
  • 反向代理缓存和负载均衡器

操作的目标

常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。缓存的关键主要包括 request method 和目标 URI(一般只有 GET 请求才会被缓存)。

缓存控制

没有缓存

缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。

1
Cache-Control: no-store

缓存但重新验证

每次有请求发出时,缓存会将此请求发到服务器,服务器端会验证请求中所描述的缓存是否过期,若未过期,则缓存才使用本地缓存副本。

1
Cache-Control: no-cache

私有和公共缓存

  • “public” 指令表示该响应可以被任何中间人(译者注:比如中间代理、CDN 等)缓存。
  • “private” 则表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。
1
2
Cache-Control: private
Cache-Control: public

过期

过期机制中,最重要的指令是 "max-age=<seconds>",表示资源能够被缓存(保持新鲜)的最大时间。相对 Expires 而言,max-age 是距离请求发起的时间的秒数。针对应用中那些不会改变的文件,通常可以手动设置一定的时长以保证缓存有效,例如图片、cssjs 等静态资源。

1
Cache-Control: max-age=31536000

s-maxage仅适用于共享缓存(CDN),优先级高于 max-ageExpires

1
Cache-Control: s-maxage=3600

验证方式

当使用了 “must-revalidate” 指令,那就意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。

1
Cache-Control: must-revalidate

Pragma 头

Pragma 是 HTTP/1.0 标准中定义的一个 header 属性,请求中包含 Pragma 的效果跟在头信息中定义 Cache-Control: no-cache 相同,但是 HTTP 的响应头没有明确定义这个属性,所以它不能拿来完全替代 HTTP/1.1 中定义的 Cache-control 头。通常定义 Pragma 以向后兼容基于 HTTP/1.0 的客户端。

新鲜度

理论上来讲,当一个资源被缓存存储后,该资源应该可以被永久存储在缓存中。

缓存驱逐

由于缓存只有有限的空间用于存储资源副本,所以缓存会定期地将一些副本删除。当服务器上面的资源进行了更新,那么缓存中的对应资源也应该被更新,由于 HTTP 是 C/S 模式的协议,服务器更新一个资源时,不可能直接通知客户端更新缓存,所以双方必须为该资源约定一个过期时间,在该过期时间之前,该资源(缓存副本)就是新鲜的,当过了过期时间后,该资源(缓存副本)则变为陈旧的。驱逐算法用于将陈旧的资源(缓存副本)替换为新鲜的,注意,一个陈旧的资源(缓存副本)是不会直接被清除或忽略的,当客户端发起一个请求时,缓存检索到已有一个对应的陈旧资源(缓存副本),则缓存会先将此请求附加一个 If-None-Match 头,然后发给目标服务器,以此来检查该资源副本是否是依然还是算新鲜的,若服务器返回了 304 (Not Modified)(该响应不会有带有实体信息),则表示此资源副本是新鲜的,这样一来,可以节省一些带宽。若服务器通过 If-None-Match 或 If-Modified-Since 判断后发现已过期,那么会带有该资源的实体内容返回。
缓存驱逐过程

缓存的先后顺序

对于含有特定头信息的请求,会去计算缓存寿命。

  • max-age:Cache-control: max-age=N 的头,相应的缓存的寿命就是 N。
  • expires:通过比较 Expires 的值和头里面 Date 属性的值来判断是否缓存还有效。
  • Last-Modified: 缓存的寿命就等于头里面 Date 的值减去 Last-Modified 的值除以 10(注:根据 rfc2626 其实也就是乘以 10%)
1
expirationTime = responseTime + freshnessLifetime - currentAge
注:responseTime 表示浏览器接收到此响应的那个时间点。

缓存验证

用户点击刷新按钮时会开始缓存验证。

  • 如果缓存的响应头信息里含有”Cache-control: must-revalidate”的定义,在浏览的过程中也会触发缓存验证。
  • 在浏览器偏好设置里设置 Advanced->Cache 为强制验证缓存也能达到相同的效果。

当缓存的文档过期后,需要进行缓存验证或者重新获取资源。只有在服务器返回强校验器或者弱校验器时才会进行验证。

ETags

  • 作为缓存的一种强校验器,ETag 响应头是一个对用户代理(User Agent, 下面简称 UA)不透明(译者注:UA 无需理解,只需要按规定使用即可)的值。对于像浏览器这样的 HTTP UA,不知道 ETag 代表什么,不能预测它的值是多少。如果资源请求的响应头里含有 ETag, 客户端可以在后续的请求的头中带上 If-None-Match 头来验证缓存。
  • Last-Modified 响应头可以作为一种弱校验器。说它弱是因为它只能精确到一秒。如果响应头里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。

当向服务端发起缓存校验的请求时,服务端会返回 200 ok 表示返回正常的结果或者 304 Not Modified(不返回 body)表示浏览器可以使用本地缓存文件。304 的响应头也可以同时更新缓存文档的过期时间。

浏览器刷新

  • 地址栏输入地址回车:走正常的缓存流程
  • 刷新页面:让强缓存过期,走协商缓存逻辑
  • 强制刷新页面: 让强缓存和协商缓存都过期,不走缓存逻辑

CDN

内容分发网络,分为推和拉模式。

  • 推模式:有更新时会强制推送到网络的各个节点
  • 拉模式:有请求时会判断当前节点是否有数据且数据是否过期,然后再向源站请求资源

时间计算

拉模式下,所有节点的缓存都设置 5 分钟,浏览器可能拉到前 10 分钟到当前时间的资源(假设当前节点到源节点之间没有中间节点),原因如下

  • 浏览器到当前节点数据缓存 5 分钟
  • 当前节点到源数据可能缓存 5 分钟