到底我们日常工作中会用到哪些缓存呢,用户操

作者: 前端  发布:2019-11-21

浏览器缓存机制剖析

2017/04/17 · 基础技术 · 缓存

原文出处: louis   

缓存一直是前端优化的主战场, 利用好缓存就成功了一半. 本篇从http请求和响应的头域入手, 让你对浏览器缓存有个整体的概念. 最终你会发现强缓存, 协商缓存 和 启发式缓存是如此的简单.

1. 缓存的分类

  1. 缓存分为服务端缓存和客户端缓存
  2. 服务端缓存又分为代理服务器缓存和反向代理服务器缓存(也叫网关缓存,比如Nginx反向代理等)其实广泛使用的CDN也是一种服务器缓存;
  3. 客户端的缓存一般指的浏览器缓存,目的就是加速各种静态资源的访问;

缓存一直以来都是一个老生常谈的问题,在工作和面试中都经常会被问到,合理利用了缓存可以提高网站的访问速度,节省带宽,减轻服务器压力,增强用户体验。到底我们日常工作中会用到哪些缓存呢?

导读

我不知道拖延症是有多严重, 反正去年3月开的题, 直到今年4月才开始写.(请尽情吐槽吧)

浏览器对于请求资源, 拥有一系列成熟的缓存策略. 按照发生的时间顺序分别为存储策略, 过期策略, 协商策略, 其中存储策略在收到响应后应用, 过期策略, 协商策略在发送请求前应用. 流程图如下所示.

9159.com 1

废话不多说, 我们先来看两张表格.

1.http header中与缓存有关的key.

key 描述 存储策略 过期策略 协商策略
Cache-Control 指定缓存机制,覆盖其它设置 ✔️ ✔️
Pragma http1.0字段,指定缓存机制 ✔️
Expires http1.0字段,指定缓存的过期时间 ✔️
Last-Modified 资源最后一次的修改时间 ✔️
ETag 唯一标识请求资源的字符串 ✔️

2.缓存协商策略用于重新验证缓存资源是否有效, 有关的key如下.

key 描述
If-Modified-Since 缓存校验字段, 值为资源最后一次的修改时间, 即上次收到的Last-Modified值
If-Unmodified-Since 同上, 处理方式与之相反
If-Match 缓存校验字段, 值为唯一标识请求资源的字符串, 即上次收到的ETag值
If-None-Match 同上, 处理方式与之相反

下面我们来看下各个头域(key)的作用.

2. 客户端的缓存机制

  1. 浏览器缓存控制机制有两种:HTML Meta标签 vs HTTP头信息
  2. HTTP的缓存策略

    1. expires策略:Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。
    2. Cache-control策略:Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。

      1. 值可以是public、private、no-cache、no- store、no-transform、must-revalidate、proxy-revalidate、max-age各个消息中的指令含义如下
        1. public 指示响应可被任何缓存区缓存
        2. private 指示对于单个用户的整个或部分消息,不能共享缓存;
        3. no-cache 相当于max-age:0,must-revalidate即资源被缓存, 但是缓存立刻过期, 同时下次访问时强制验证资源有效性
        4. no-store 请求和响应都不缓存
        5. max-age:指客户机可以接受生存期不大于指定时间的响应
        6. min-fresh:指客户机可以接受响应时间小于当前时间加上指定时间的响应
        7. max-stale:指客户机可以接受超出超时期间的响应消息;
      2. Last-Modified/If-Modified-Since:Last-Modified/If-Modified-Since要配合Cache-Control使用。

        1. Last-Modified:标示这个响应资源的最后修改时间

        2. If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。缓存校验字段, 其值为上次响应头的Last-Modified值, 若与请求资源当前的Last-Modified值相同, 那么将返回304状态码的响应, 反之, 将返回200状态码响应.

          当max-age 与 max-stale 和 min-fresh 同时使用时, 它们的设置相互之间独立生效, 最为保守的缓存策略总是有效. 这意味着, 如果max-age=10 days, max-stale=2 days, min-fresh=3 days, 那么:
          根据max-age的设置, 覆盖原缓存周期, 缓存资源将在4月15日失效(5+10=15);
          根据max-stale的设置, 缓存过期后两天依然有效, 此时响应将返回110(Response is stale)状态码, 缓存资源将在4月14日失效(12+2=14);
          根据min-fresh的设置, 至少要留有3天的新鲜期, 缓存资源将在4月9日失效(12-3=9);
          由于客户端总是采用最保守的缓存策略, 因此, 4月9日后, 对于该资源的请求将重新向服务器发起验证.

      3. Etag/If-None-Match:Etag/If-None-Match也要配合Cache-Control使用。

        1. Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。
        2. If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。
      4. 既生Last-Modified何生Etag?你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:
        1. Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
        2. Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag一起使用时,服务器会优先验证ETag。
        3. yahoo的Yslow法则中则提示谨慎设置Etag:需要注意的是分布式系统里多台机器间文件的last-modified必须保持一致,以免负载均衡到不同机器导致比对失败,Yahoo建议分布式系统尽量关闭掉Etag(每台机器生成的etag都会不一样,因为除了 last-modified、inode 也很难保持一致)。
  • 数据库缓存:就是将查询的数据放到内存中,下次查询直接从内存中读取,提高查询效率。
  • CDN缓存。
  • 代理服务器缓存:浏览器和源服务器之间的中间服务器,运作原理跟HTTP缓存差不多,但是规模更大。
  • 浏览器缓存:每个浏览器都实现了HTTP缓存,我们今天的重点。
  • 应用层缓存。
  • cookie,web storage等。

Cache-Control

浏览器缓存里, Cache-Control是金字塔顶尖的规则, 它藐视一切其他设置, 只要其他设置与其抵触, 一律覆盖之.

不仅如此, 它还是一个复合规则, 包含多种值, 横跨 存储策略, 过期策略 两种, 同时在请求头和响应头都可设置.

语法为: “Cache-Control : cache-directive”.

Cache-directive共有如下12种(其中请求中指令7种, 响应中指令9种):

Cache-directive 描述 存储策略 过期策略 请求字段 响应字段
public 资源将被客户端和代理服务器缓存 ✔️ ✔️
private 资源仅被客户端缓存, 代理服务器不缓存 ✔️ ✔️
no-store 请求和响应都不缓存 ✔️ ✔️ ✔️
no-cache 相当于max-age:0,must-revalidate即资源被缓存, 但是缓存立刻过期, 同时下次访问时强制验证资源有效性 ✔️ ✔️ ✔️ ✔️
max-age 缓存资源, 但是在指定时间(单位为秒)后缓存过期 ✔️ ✔️ ✔️ ✔️
s-maxage 同上, 依赖public设置, 覆盖max-age, 且只在代理服务器上有效. ✔️ ✔️ ✔️
max-stale 指定时间内, 即使缓存过时, 资源依然有效 ✔️ ✔️
min-fresh 缓存的资源至少要保持指定时间的新鲜期 ✔️ ✔️
must-revalidation / proxy-revalidation 如果缓存失效, 强制重新向服务器(或代理)发起验证(因为max-stale等字段可能改变缓存的失效时间) ✔️ ✔️
only-if-cached 仅仅返回已经缓存的资源, 不访问网络, 若无缓存则返回504 ✔️
no-transform 强制要求代理服务器不要对资源进行转换, 禁止代理服务器对 Content-Encoding, Content-Range, Content-Type字段的修改(因此代理的gzip压缩将不被允许) ✔️ ✔️

假设所请求资源于4月5日缓存, 且在4月12日过期.

当max-age 与 max-stale 和 min-fresh 同时使用时, 它们的设置相互之间独立生效, 最为保守的缓存策略总是有效. 这意味着, 如果max-age=10 days, max-stale=2 days, min-fresh=3 days, 那么:

  • 根据max-age的设置, 覆盖原缓存周期, 缓存资源将在4月15日失效(5+10=15);
  • 根据max-stale的设置, 缓存过期后两天依然有效, 此时响应将返回110(Response is stale)状态码, 缓存资源将在4月14日失效(12+2=14);
  • 根据min-fresh的设置, 至少要留有3天的新鲜期, 缓存资源将在4月9日失效(12-3=9);

由于客户端总是采用最保守的缓存策略, 因此, 4月9日后, 对于该资源的请求将重新向服务器发起验证.

3. 几个重要概念的解释

  1. vary

    “vary”本身是“变化”的意思,而在http报文中更趋于是“vary from”(与。。。不同)的含义,它表示服务端会以什么基准字段来区分、筛选缓存版本。我们先考虑这么一个问题——在服务端有着这么一个地址,如果是IE用户则返回针对IE开发的内容,否则返回另一个主流浏览器版本的内容。这很简单,服务端获取到请求的 User-Agent 字段做处理即可。但是用户请求的是代理服务器而非原服务器,且代理服务器如果直接把缓存的IE版本资源发给了非IE的客户端,这就出问题了。

    因此 Vary 便是着手处理该问题的首部字段,我们可以在响应报文加上:
    9159.com ,Vary: User-Agent
    便能知会代理服务器需要以 User-Agent 这个请求首部字段来区别缓存版本,防止传递给客户端的缓存不正确。

    Vary 也接受条件组合的形式:

    Vary: User-Agent, Accept-Encoding

  2. Data和Age

    1. HTTP并没有提供某种方法来帮用户区分其收到的资源是否命中了代理服务器的缓存,但在客户端我们可以通过计算响应报文中的 Date 和 Age 字段来得到答案。
    2. Date 理所当然是原服务器发送该资源响应报文的时间(GMT格式),如果你发现 Date 的时间与“当前时间”差别较大,或者连续F5刷新发现 Date 的值都没变化,则说明你当前请求是命中了代理服务器的缓存。上述的“当前时间”自然是相对于原服务器而言的时间,那么如何获悉原服务器的当前时间呢?
    3. 每次你刷新页面,浏览器都会重新发出这条url的请求,你会发现其 Date 值是不断变化的,这说明该链接没有命中缓存,都是从原服务器返回过来的数据。因此我们可以拿页面上其它静态资源请求回包中的 Date 与其进行对比,若静态资源的 Date 早于原服务端时间,则说明命中了代理服务器缓存。
    4. 这里的 Age 也是响应报文中的首部字段,它表示该文件在代理服务器中存在的时间(秒),如文件被修改或替换,Age会重新由0开始累计。

了解了我们日常生活中常用的缓存机制后(当然还有更多),今天我们重点来学习下HTTP缓存,它可是面试过程中必问的问题,想进BAT的话,必须把它啃透,废话不多说,直接进入正题。

Pragma

http1.0字段, 通常设置为Pragma:no-cache, 作用同Cache-Control:no-cache. 当一个no-cache请求发送给一个不遵循HTTP/1.1的服务器时, 客户端应该包含pragma指令. 为此, 勾选☑️ 上disable cache时, 浏览器自动带上了pragma字段. 如下:

9159.com 2

4. 强制缓存和协商缓存

  1. 强制缓存:在缓存数据未失效的情况下,可以直接使用缓存数据
  2. 协商缓存:缓存过期之后,继续请求该资源,对于现代的浏览器有两种方法:
    1. ETag值进行比较,与IF-NONE-MATCH进行比较
    2. Modified_value和If-Modified-Since字段进行比较;
  3. 以上, ETag优先级比Last-Modified高, 同时存在时, 前者覆盖后者.

什么是浏览器缓存?

浏览器缓存是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。
可能有同学会问是不是我们每次获取资源都必须发送HTTP请求到服务器?答案是不是地,这里就涉及到浏览器缓存的分类。

Expires

JavaScript

Expires:Wed, 05 Apr 2017 00:55:35 GMT

1
Expires:Wed, 05 Apr 2017 00:55:35 GMT

即到期时间, 以服务器时间为参考系, 其优先级比 Cache-Control:max-age 低, 两者同时出现在响应头时, Expires将被后者覆盖. 如果Expires, Cache-Control: max-age, 或 Cache-Control:s-maxage 都没有在响应头中出现, 并且也没有其它缓存的设置, 那么浏览器默认会采用一个启发式的算法, 通常会取响应头的Date_value - Last-Modified_value值的10%作为缓存时间.

如下资源便采取了启发式缓存算法.

9159.com 3

其缓存时间为 (Date_value - Last-Modified_value) * 10%, 计算如下:

JavaScript

const Date_value = new Date('Thu, 06 Apr 2017 01:30:56 GMT').getTime(); const LastModified_value = new Date('Thu, 01 Dec 2016 06:23:23 GMT').getTime(); const cacheTime = (Date_value - LastModified_value) / 10; const Expires_timestamp = Date_value + cacheTime; const Expires_value = new Date(Expires_timestamp); console.log('Expires:', Expires_value); // Expires: Tue Apr 18 2017 23:25:41 GMT+0800 (CST)

1
2
3
4
5
6
const Date_value = new Date('Thu, 06 Apr 2017 01:30:56 GMT').getTime();
const LastModified_value = new Date('Thu, 01 Dec 2016 06:23:23 GMT').getTime();
const cacheTime = (Date_value - LastModified_value) / 10;
const Expires_timestamp = Date_value + cacheTime;
const Expires_value = new Date(Expires_timestamp);
console.log('Expires:', Expires_value); // Expires: Tue Apr 18 2017 23:25:41 GMT+0800 (CST)

可见该资源将于2017年4月18日23点25分41秒过期, 尝试以下两步进行验证:

1) 试着把本地时间修改为2017年4月18日23点25分40秒, 迅速刷新页面, 发现强缓存依然有效(依旧是200 OK (from disk cache)).

2) 然后又修改本地时间为2017年4月18日23点26分40秒(即往后拨1分钟), 刷新页面, 发现缓存已过期, 此时浏览器重新向服务器发起了验证, 且命中了304协商缓存, 如下所示.

9159.com 4

3) 将本地时间恢复正常(即 2017-04-06 09:54:19). 刷新页面, 发现Date依然是4月18日, 如下所示.

9159.com 5

⚠️ Provisional headers are shown 和Date字段可以看出来, 浏览器并未发出请求, 缓存依然有效, 只不过此时Status Code显示为200 OK. (甚至我还专门打开了charles, 也没有发现该资源的任何请求, 可见这个200 OK多少有些误导人的意味)

可见, 启发式缓存算法采用的缓存时间可长可短, 因此对于常规资源, 建议明确设置缓存时间(如指定max-age 或 expires).

5. 刷新的不同的操作缓存的生成

9159.com 6

用户操作缓存机制.png

浏览器缓存的分类

浏览器缓存分为两大类:强缓存和协商缓存。

强缓存就是不需要发送HTTP请求道服务器,直接从本地磁盘获取缓存过的资源。它是利用HTTP响应报文中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间。

Expires:该字段是HTTP/1.0时的规范,它的值是一个绝对时间的GMT格式的时间字符串,如Expires: Mon, 06 Feb 2017 08:26:48 GMT。这个时间代表资源的失效时间,在此之间,即命中强缓存。但是它有一个明显的缺点,当客户端与服务器时间出现较大偏差,就会出现混乱。

Cache-Control:为了解决Expires出现的问题,HTTP/1.1添加了Cache-Control。主要是利用max-age来进行判断,它是一个相对时间,如Cache-Control:max-age=600,代表着资源的有效期是600秒(10分钟)。除了max-age外,Cache-Control还有以下几个常用的值:

  • no-cache:不适用强缓存。需要使用缓存协商。
  • no-store:禁止浏览器缓存,不适用强缓存和缓存协商,每次请求资源都需要发送HTTP到服务器,每次都需要下载完整的资源。
  • public:可以被所有的用户缓存,包括客户端和CDN等中间代理服务器。
  • private:只允许客户端缓存,不允许CDN等中间代理服务器对其缓存。

Cache-Control与Expires可以在服务端配置同时启用,但是Cache-Control的优先级高于Expires。

协商缓存需要由服务器来确定客户端缓存资源是否可用。这主要涉及Header中两组字段:Last-Modified/If-Modified-Since或ETag/If-None-Match,这两组字段都是成对出现的。若第一次的响应头没有Last-Modified或ETag,则后续的请求头部也不会有If-Modified-Since或If-None-Match。

Last-Modified/If-Modified-Since:浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modified,它是一个时间标识该资源的最后最后修改时间。当浏览器再次请求该资源时,HTTP请求头部会带上If-Modified-Since,该值为上次响应报文头部的Last-Modified的值,服务器接收到If-Modified-Since,会根据资源的最后修改时间来判断是否命中协商缓存,如果命中,返回304,并且不会返回Last-Modified和无响应body。否则返回200。

ETag/If-None-Match:它们的值不是一个时间标识,而是一个校验码。ETag可以保证每一个资源都是唯一的,资源变化都会导致ETag变化,服务器根据接收到的If-None-Match来判断是否命中协商缓存。但是当服务器返回304的时候,由于ETag重新生成过,响应头部也会带上ETag,即使它跟之前的没有变化。

为什么要有ETag?不是已经有Last-Modified吗。

  • 一些文件或许会周期性的修改,但是它的内容没有变化(只是改变了修改时间),这个时候我们并不希望客户端认为这个文件修改了,而重新获取。
  • 某些文件在1秒内修改了N次,用If-Modified-Since无法进行区分。
  • 某些服务器不能精确的得到文件的最后修改时间。
    Last-Modified和ETag可以一起使用,但是ETag的优先级大于Last-Modified,当ETag相同的情况下,才会继续比较Last-Modified,最后才决定是否返回304。

看了这么多不知道你糊涂没,下面有两张图,通过这两张图,你能对浏览器的缓存策略有一个新的认识。

9159.com 7

第一次HTTP请求

第一次打开网站的时候,因为本地没有缓存,所以必须向服务器发起HTTP请求并下载所需资源,服务器返回的响应报文头部中可带相关字段来表明采取何种缓存策略。

9159.com 8

再次HTTP请求

当浏览器再次打开网站的时候,如果服务器设置了资源不可以缓存的的话(Cache-Control:no-store)则跟第一次HTTP请求一样;如果该资源可以被缓存,先判断资源是否过期,即检查Cache-Control:max-age或Expires,没过期的话,直接从本地缓存中读取,过期的话则发送一个HTTP请求到服务器,服务器根据ETag和Last-Modified来决定返回304还是200。

ETag

XHTML

ETag:"fcb82312d92970bdf0d18a4eca08ebc7efede4fe"

1
ETag:"fcb82312d92970bdf0d18a4eca08ebc7efede4fe"

实体标签, 服务器资源的唯一标识符, 浏览器可以根据ETag值缓存数据, 节省带宽. 如果资源已经改变, etag可以帮助防止同步更新资源的相互覆盖. ETag 优先级比 Last-Modified 高.

6. 图标解释缓存机制

9159.com 9

HTTP缓存图.png

If-Match

语法: If-Match: ETag_value 或者 If-Match: ETag_value, ETag_value, …

缓存校验字段, 其值为上次收到的一个或多个etag 值. 常用于判断条件是否满足, 如下两种场景:

  • 对于 GET 或 HEAD 请求, 结合 Range 头字段, 它可以保证新范围的请求和前一个来自相同的源, 如果不匹配, 服务器将返回一个416(Range Not Satisfiable)状态码的响应.
  • 对于 PUT 或者其他不安全的请求, If-Match 可用于阻止错误的更新操作, 如果不匹配, 服务器将返回一个412(Precondition Failed)状态码的响应.

If-None-Match

语法: If-None-Match: ETag_value 或者 If-None-Match: ETag_value, ETag_value, …

缓存校验字段, 结合ETag字段, 常用于判断缓存资源是否有效, 优先级比If-Modified-Since高.

  • 对于 GET 或 HEAD 请求, 如果其etags列表均不匹配, 服务器将返回200状态码的响应, 反之, 将返回304(Not Modified)状态码的响应. 无论是200还是304响应, 都至少返回 Cache-Control, Content-Location, Date, ETag, Expires, and Vary 中之一的字段.
  • 对于其他更新服务器资源的请求, 如果其etags列表匹配, 服务器将执行更新, 反之, 将返回412(Precondition Failed)状态码的响应.

Last-Modified

语法: Last-Modified: 星期,日期 月份 年份 时:分:秒 GMT

JavaScript

Last-Modified: Tue, 04 Apr 2017 10:01:15 GMT

1
Last-Modified: Tue, 04 Apr 2017 10:01:15 GMT

用于标记请求资源的最后一次修改时间, 格式为GMT(格林尼治标准时间). 如可用 new Date().toGMTString()获取当前GMT时间. Last-Modified 是 ETag 的fallback机制, 优先级比 ETag 低, 且只能精确到秒, 因此不太适合短时间内频繁改动的资源. 不仅如此, 服务器端的静态资源, 通常需要编译打包, 可能出现资源内容没有改变, 而Last-Modified却改变的情况.

If-Modified-Since

语法同上, 如:

JavaScript

If-Modified-Since: Tue, 04 Apr 2017 10:12:27 GMT

1
If-Modified-Since: Tue, 04 Apr 2017 10:12:27 GMT

缓存校验字段, 其值为上次响应头的Last-Modified值, 若与请求资源当前的Last-Modified值相同, 那么将返回304状态码的响应, 反之, 将返回200状态码响应.

If-Unmodified-Since

缓存校验字段, 语法同上. 表示资源未修改则正常执行更新, 否则返回412(Precondition Failed)状态码的响应. 常用于如下两种场景:

  • 不安全的请求, 比如说使用post请求更新wiki文档, 文档未修改时才执行更新.
  • 与 If-Range 字段同时使用时, 可以用来保证新的片段请求来自一个未修改的文档.

强缓存

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

9159.com 10

9159.com 11

对于常规请求, 只要存在该资源的缓存, 且Cache-Control:max-age 或者expires没有过期, 那么就能命中强缓存.

协商缓存

缓存过期后, 继续请求该资源, 对于现代浏览器, 拥有如下两种做法:

  • 根据上次响应中的ETag_value, 自动往request header中添加If-None-Match字段. 服务器收到请求后, 拿If-None-Match字段的值与资源的ETag值进行比较, 若相同, 则命中协商缓存, 返回304响应.
  • 根据上次响应中的Last-Modified_value, 自动往request header中添加If-Modified-Since字段. 服务器收到请求后, 拿If-Modified-Since字段的值与资源的Last-Modified值进行比较, 若相同, 则命中协商缓存, 返回304响应.

以上, ETag优先级比Last-Modified高, 同时存在时, 前者覆盖后者. 下面通过实例来理解下强缓存和协商缓存.

如下忽略首次访问, 第二次通过 If-Modified-Since 命中了304协商缓存.

9159.com 12

协商缓存的响应结果, 不仅验证了资源的有效性, 同时还更新了浏览器缓存. 主要更新内容如下:

XHTML

Age:0 Cache-Control:max-age=600 Date: Wed, 05 Apr 2017 13:09:36 GMT Expires:Wed, 05 Apr 2017 00:55:35 GMT

1
2
3
4
Age:0
Cache-Control:max-age=600
Date: Wed, 05 Apr 2017 13:09:36 GMT
Expires:Wed, 05 Apr 2017 00:55:35 GMT

Age:0 表示命中了代理服务器的缓存, age值为0表示代理服务器刚刚刷新了一次缓存.

Cache-Control:max-age=600 覆盖 Expires 字段, 表示从Date_value, 即 Wed, 05 Apr 2017 13:09:36 GMT 起, 10分钟之后缓存过期. 因此10分钟之内访问, 将会命中强缓存, 如下所示:

9159.com 13

当然, 除了上述与缓存直接相关的字段外, http header中还包括如下间接相关的字段.

Age

出现此字段, 表示命中代理服务器的缓存. 它指的是代理服务器对于请求资源的已缓存时间, 单位为秒. 如下:

Age:2383321 Date:Wed, 08 Mar 2017 16:12:42 GMT

1
2
Age:2383321
Date:Wed, 08 Mar 2017 16:12:42 GMT

以上指的是, 代理服务器在2017年3月8日16:12:42时向源服务器发起了对该资源的请求, 目前已缓存了该资源2383321秒.

Date

指的是响应生成的时间. 请求经过代理服务器时, 返回的Date未必是最新的, 通常这个时候, 代理服务器将增加一个Age字段告知该资源已缓存了多久.

Vary

对于服务器而言, 资源文件可能不止一个版本, 比如说压缩和未压缩, 针对不同的客户端, 通常需要返回不同的资源版本. 比如说老式的浏览器可能不支持解压缩, 这个时候, 就需要返回一个未压缩的版本; 对于新的浏览器, 支持压缩, 返回一个压缩的版本, 有利于节省带宽, 提升体验. 那么怎么区分这个版本呢, 这个时候就需要Vary了.

服务器通过指定Vary: Accept-Encoding, 告知代理服务器, 对于这个资源, 需要缓存两个版本: 压缩和未压缩. 这样老式浏览器和新的浏览器, 通过代理, 就分别拿到了未压缩和压缩版本的资源, 避免了都拿同一个资源的尴尬.

Vary:Accept-Encoding,User-Agent

1
Vary:Accept-Encoding,User-Agent

如上设置, 代理服务器将针对是否压缩和浏览器类型两个维度去缓存资源. 如此一来, 同一个url, 就能针对PC和Mobile返回不同的缓存内容.

怎么让浏览器不缓存静态资源

实际上, 工作中很多场景都需要避免浏览器缓存, 除了浏览器隐私模式, 请求时想要禁用缓存, 还可以设置请求头: Cache-Control: no-cache, no-store, must-revalidate .

当然, 还有一种常用做法: 即给请求的资源增加一个版本号, 如下:

XHTML

<link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/>

1
<link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/>

这样做的好处就是你可以自由控制什么时候加载最新的资源.

不仅如此, HTML也可以禁用缓存, 即在页面的

节点中加入标签, 代码如下:

XHTML

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>

1
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>

上述虽能禁用缓存, 但只有部分浏览器支持, 而且由于代理不解析HTML文档, 故代理服务器也不支持这种方式.

IE8的异常表现

实际上, 上述缓存有关的规律, 并非所有浏览器都完全遵循. 比如说IE8.

资源缓存是否有效相关.

浏览器 前提 操作 表现 正常表现
IE8 资源缓存有效 新开一个窗口加载网页 重新发送请求(返回200) 展示缓存的页面
IE8 资源缓存失效 原浏览器窗口中单击 Enter 按钮 展示缓存的页面 重新发送请求(返回200)

Last-Modified / E-Tag 相关.

浏览器 前提 操作 表现 正常表现
IE8 资源内容没有修改 新开一个窗口加载网页 浏览器重新发送请求(返回200) 重新发送请求(返回304)
IE8 资源内容已修改 原浏览器窗口中单击 Enter 按钮 浏览器展示缓存的页面 重新发送请求(返回200)

参考文章

  • Cache Policy Interaction—Maximum Age and Maximum Staleness.aspx)
  • HTTP/1.1: Header Field Definitions
  • http – What’s the difference between Cache-Control: max-age=0 and no-cache? – Stack Overflow
  • App 缓存方案:Http 缓存 · baitouwei
  • Cache-Control – HTTP | MDN
  • 彻底弄懂 Http 缓存机制 – 基于缓存策略三要素分解法&version=12010110&nettype=WIFI&fontScale=100&pass_ticket=n3plsW%2FV7Vb6O9hKzPNig5MYpXUoJo3tNUNxhJ5Jh6e9AS%2BRXmvJPbIzUeUmL3S2)

    1 赞 7 收藏 评论

9159.com 14

本文由9159.com发布于前端,转载请注明出处:到底我们日常工作中会用到哪些缓存呢,用户操

关键词: