浏览器缓存种类

缓存的种类有很多,其大致可归为两类:私有与共享缓存。
共享缓存存储的响应能够被多个用户使用。
私有缓存只能用于单独用户。
本文将主要介绍浏览器与代理缓存,除此之外还有网关缓存、CDN、反向代理缓存和负载均衡器等部署在服务器上的缓存方式,为站点和 web 应用提供更好的稳定性、性能和扩展性。

私有缓存

私有缓存只能用于单独用户。
你可能已经见过浏览器设置中的“缓存”选项。浏览器缓存拥有用户通过 HTTP 下载的所有文档。这些缓存为浏览过的文档提供向后/向前导航,保存网页,查看源码等功能,可以避免再次向服务器发起多余的请求。它同样可以提供缓存内容的离线浏览。

共享缓存

共享缓存可以被多个用户使用。
例如,ISP 或你所在的公司可能会架设一个 web 代理来作为本地网络基础的一部分提供给用户。这样热门的资源就会被重复使用,减少网络拥堵与延迟。

浏览器缓存控制

Cache-Control

  • 没有缓存
  • 缓存但重新验证
  • 公有和私有缓存
  • 过期
  • 验证方式:当使用了 “must-revalidate” 指令,那就意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。

    Pragma

    PragmaHTTP/1.0标准中定义的一个header属性,请求中包含Pragma的效果跟在头信息中定义Cache-Control: no-cache相同。

    新鲜度

    理论上来讲,当一个资源被缓存存储后,该资源应该可以被永久存储在缓存中。
    由于缓存只有有限的空间用于存储资源副本,所以缓存会定期地将一些副本删除,这个过程叫做缓存驱逐。
    驱逐算法用于将陈旧的资源(缓存副本)替换为新鲜的。

    缓存验证

    如果缓存的响应头信息里含有 Cache-control: must-revalidate的定义,在浏览的过程中也会触发缓存验证。
    当缓存的文档过期后,需要进行缓存验证或者重新获取资源。只有在服务器返回强校验器或者弱校验器时才会进行验证。

    ETag

    如果资源请求的响应头里含有ETag, 客户端可以在后续的请求的头中带上 If-None-Match 头来验证缓存。
    Last-Modified 响应头可以作为一种弱校验器。说它弱是因为它只能精确到一秒。如果响应头里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。
    当向服务端发起缓存校验的请求时,服务端会返回 200 ok表示返回正常的结果或者 304 Not Modified(不返回body)表示浏览器可以使用本地缓存文件。304的响应头也可以同时更新缓存文档的过期时间。

    Vary

    当缓存服务器收到一个请求,只有当前的请求和原始(缓存)的请求头跟缓存的响应头里的Vary都匹配,才能使用缓存的响应。

    HTTP 首部字段

    HTTP 首部与缓存有关的字段主要有以下几种:
key 说明 存储策略 过期策略 协商策略
Cache-Control 指定缓存机制
- [x]

|
- [x]

| | | Pragma | HTTP1.1之前版本使用,已经废弃 |
- [x]

| | | | Expires | | |
- [x]

| | | Last-Modified | | | |
- [x]

| | ETag | | | |
- [x]

|

协商缓存字段有:

  • If-Modified-Since / If-Unmodified-Since
  • If-None-Match / If-Match

这几个字段也是要求我们必须掌握的,接下来具体讲一下这几个字段的用法。

HTTP/1.1 通用首部字段

1. Cache-Control

Cache-Control 的语法为:2. 浏览器缓存原理 - 图1
cache-directive 共有如下12种(其中请求中指令7种, 响应中指令 9 种)

cache-directive 存储策略 过期策略 请求字段 响应字段
public
- [x]

| | |
- [x]

| | private |
- [x]

| | |
- [x]

| | no-store |
- [x]

| |
- [x]

|
- [x]

| | no-cache |
- [x]

|
- [x]

|
- [x]

|
- [x]

| | max-age |
- [x]

|
- [x]

|
- [x]

|
- [x]

| | s-maxage |
- [x]

|
- [x]

| |
- [x]

| | max-stale | |
- [x]

| | | | min-fresh | |
- [x]

|
- [x]

| | | must-revalidate | |
- [x]

|
- [x]

|
- [x]

| | proxy-revalidate | |
- [x]

| |
- [x]

| | only-if-cached | | |
- [x]

| | | no-transform | | |
- [x]

|
- [x]

|

缓存请求指令:

cache-directive 参数 说明
no-store 不缓存请求或响应的任何内容
no-cache 强制向服务器资源再次验证
max-age = 秒 必须 响应的最大 Age 值
max-stale(=秒) 可缺省 接收已过期的响应
min-fresh = 秒 必须 期望在指定时间内响应仍有效
only-if-cached 从缓存获取资源
no-transform 代理不可更改媒体类型
cache-exetension - 新指令标记 token

缓存响应指令:

cache-directive 参数 说明
public 可向任意方提供响应的缓存
private 可省略 仅向特定用户返回响应
no-store 不缓存请求或响应任何内容
no-cache 可省略 缓存前必须先确认其有效性
max-age = 秒 必须 响应的最大 Age 值
s-maxage = 秒 必须 公共缓存服务器响应的最大 Age 值
must-revalidate 可缓存,但必须向源服务器进行确认
proxy-revalidate 要求中间缓存服务器对缓存响应的有效性再进行确认
no-transform 代理不可更改媒体类型
cache-extension - 新指令标记
  1. no-cache 与 no-store :::info
  • no-cache:可以缓存,但是需要像服务器确认。防止缓存过期的资源。
  • no-store:不缓存请求或响应的任意内容。 :::

    1. max-age 和 s-maxage :::info
  • s-maxage 只适用共享缓存,对于向同一用户重复返回的响应来说,这个指令没有任何作用。

  • max-age:缓存时间在 max-age 范围内,不用向源服务器确认。
  • 当 max-age 值为 0 时,缓存服务器通常要将请求转发给源服务器。
  • 优先级比 Expires 高。 ::: 优先级关系:s-maxage > max-age > Expries
    上文提到了共享缓存,那共享缓存和私有缓存有什么区别呢?
    私有缓存:仅供一个用户使用的缓存,通常只存在于用户的浏览器上
    共享缓存:可以提供多个用户的缓存,存在于网络中负责转发消息的代理服务器中(对热点资源常使用共享缓存,以减轻服务器的压力,并提升网络效率)
    像正向代理和反向代理,都可以用到共享缓存,但 Authentication 响应是不可以被代理服务器缓存的。

    1. public:明确表明其他用户也可利用缓存。
    2. private:则明确表明其他用户也可利用缓存,与 public 行为相反。
    3. min-fresh:单位是秒,超过 min-fresh 的资源无法被响应。
    4. max-stale:单位是秒,如果 max-stale 有值,即使缓存过期,只要在 max-stale内,就会被客户端接收。如果 max-stale 没有值,则表示无论经过多久,客户端都会接收响应。
    5. must-revalidate
    6. proxy-revalidate
    7. only-if-cached
    8. no-transform:该指令规定无论是在请求还是响应中,缓存都不能改变实体主体的媒体类型。

      2. Pragma

      :::info Pragma是HTTP/1.1之前版本的历史遗留字段,仅作为与HTTP/1.0的向后兼容而定义。
      如果中间服务器以HTTP/1.1为基准,那直接采用Cache-Control: no-cache 就可以。 :::

      3. Date

      4. Connection

      5. Trailer

      6. Transfer-Encoding

      7. Upgrade

      8. Via

      9. Warning

      请求首部字段:

      1. If-None-Match 与 If-Match

      :::info 只有在 If-None-Match 的字段值与 ETag 值不匹配时,可处理该请求。(常用)
      当 If-Match 的字段值 和 ETag 值匹配时,服务器才接收请求,处理方式与上面相反。 :::

      2. If-Modified-Since (常用)

      :::info 在 If-Modified-Since 指定的日期后,资源发生了更新,服务器才接收请求。(常用)
      If-Unmodified-Since 处理方式与上述相反。 :::

      3. If-Range

      4. Accept

      5. Accept-Charset

      6. Accept-Encoding

      7. Accept-Language

      8. Authorization

      9. Proxy-Authorization

      10. Expect

      11. Form

      12. Host

      13. Max-Forwards

      14. Range

      15. Referer

      16. TE

      17. User-Agent

      响应首部字段:

      1. ETag

      :::info 当资源发生更新时,ETag 的值也需要更新。分为强 ETag 和 弱 ETag。
      强 ETag:无论实体发生多么细微的变化,都会改变其值。
      弱 ETag:只用于提示资源是否相同。只有资源发生了根本的改变,才会改变 ETag,会在字段最开始处附加 W/ :::

      2. Age

      :::info Age 字段用户告知客户端源服务器在多久前创建了响应。单位是秒。
      如果创建响应的服务器是缓存服务器,则 Age 代表缓存后的响应再次发起认证到认证完成的时间是多少。
      代理创建响应时必须加上首部字段 Age。 :::

      3. Vary

      :::info Vary 可以对缓存进行控制。
      如果从代理服务器接收到源服务器返回包含 Vary 指定项的响应之后,若要进行缓存,则仅对请求中含有相同 Vary 的值的字段进行缓存。如下图 ::: 2. 浏览器缓存原理 - 图2

      4. WWW-Authenticate

      5. Proxy-Authenticate

      6. Location

      7. Accept-Ranges

      8. Retry-After

      9. Server

      实体首部字段:

      1. Exprice:

      :::info 将资源的失效日期返回客户端。
      如果 Cache-Control 有指定 max-age,会先处理 max-age :::

      2. Last-Modified

      :::info 指明资源最终的修改时间。 :::

      3. Allow

      4. Content-Encoding

      5. Content-Language

      6. Content-Length

      7. Content-Location

      8. Content-MD5

      9. Content-Range

      10. Content-Type

为 Cookie 服务的首部字段

1. Set-Cookie

2. Cookie

强缓存与协商缓存

:::info 控制强缓存的字段:Expires 和 Cache-Control
控制协商缓存的字段:Last-Modified / If-Modified-Since 和 Etag / If-None-Match :::

什么是强缓存

一旦资源命中强缓存, 浏览器便不会向服务器发送请求, 而是直接读取缓存. Chrome下的现象是 200 OK (from disk cache) 或者 200 OK (from memory cache)。

缓存位置

浏览器启动缓存后,下次请求时,有的是 from disk cache,有的是 from memory cache,那么浏览器是如何控制的呢?
其实这种情况是比较复杂的,常常是根据资源类型、隐私策略、各浏览器的设计哲学等而定。
Chrome官方文档曾经这么描述:

“Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, call handlerBehaviorChanged() to flush the in-memory cache. But don’t do it often; flushing the cache is a very expensive operation. You don’t need to call handlerBehaviorChanged() after registering or unregistering an event listener.”

浏览器缓存位置一般分为四类: Service Worker—>Memory Cache—>Disk Cache—>Push Cache。

1. Service Worker

是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

2. Memory Cache

内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

3. Disk Cache

存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。
memory cache 要比 disk cache 快的多。举个例子:从远程 web 服务器直接提取访问文件可能需要500毫秒(半秒),那么磁盘访问可能需要10-20毫秒,而内存访问只需要100纳秒,更高级的还有 L1缓存访问(最快和最小的 CPU 缓存)只需要0.5纳秒。

4. Push Cache

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

什么是协商缓存

浏览器向服务器发送请求,服务器会根据这个请求的请求头中的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的响应头通知浏览器从缓存中读取资源;

强缓存与协商缓存的异同

:::info 两者的共同点是,都是从客户端缓存中读取资源;
区别是强缓存不会发请求,协商缓存会发请求 :::

浏览器缓存流程

浏览器第一次加载资源,服务器返回200,浏览器将资源文件从服务器上请求下载下来,并把response header及该请求的返回时间一并缓存;
下一次加载资源时,先比较当前时间和上一次返回200时的时间差,如果没有超过cache-control设置的max-age,则没有过期,命中强缓存,不发请求直接从本地缓存读取该文件(如果浏览器不支持HTTP1.1,则用expires判断是否过期);如果时间过期,则向服务器发送header带有If-None-Match和If-Modified-Since 的请求;
服务器收到请求后,优先根据Etag的值判断被请求的文件有没有做修改,Etag值一致则没有修改,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的Etag值并返回 200;
如果服务器收到的请求没有Etag值,则将If-Modified-Since和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回304;不一致则返回新的last-modified和文件并返回 200;

实战演练

判断我们获得的响应有没有被代理服务器使用共享缓存来缓存

我们通过 age 属性来判断

参考链接

20 | 生鲜速递:HTTP的缓存控制 (geekbang.org)
31 | 复杂的Cache-Control头部 (geekbang.org)
浏览器缓存策略解析 (geekbang.org)
彻底理解浏览器的缓存机制 - 掘金 (juejin.cn)
【第903期】浏览器缓存机制剖析 (qq.com)
【第887期】浏览器的缓存机制小结 (qq.com)