HTTP缓存

对前端来说,缓存是性能优化必须面对的问题?;捍婵梢苑⑸诤芏嗟胤剑唤鱿抻阡榔?。也可以在代理服务器,网关以及数据库等。

HTTP缓存

浏览器的缓存其实跟cookie、webStorage以及IndexedDB这些技术没有关系,缓存实际上是浏览器请求资源的副本,它遵循的策略是由HTTP协议定义的。

HTTP头信息控制缓存

  • Expires

    Expires头设置缓存过期时间,超过了这个时间点就代表资源过期。但由于使用具体时间,如果时间表示出错或者没有转换到正确的时区都可能造成缓存生命周期出错。并且 Expires 是 HTTP/1.0 的标准,现在更倾向于用 HTTP/1.1 中定义的 Cache-Control。两个同时存在时也是 Cache-Control 的优先级更高。

  • Cache-Control

    Cache-Control头可以包含多个字段值。

    1. max-age,指定一个时间长度,在这个时间段内缓存是有效的,单位是s;

      image
    2. s-maxage,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略;

    3. public, 表明响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。

    4. private,表明响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。

    5. no-cache,强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器。不是字面意思上的不缓存。

    6. no-store,禁止缓存,每次请求都要向服务器重新获取数据。

  • Last-modified/If-Modified-Since

    Last-modified: 服务器端资源的最后修改时间,响应头部会带上这个标识。第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since 即为之前记录下的时间。服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。

    image
  • Etag/If-None-Match

    由服务器端上生成的一段 hash 字符串,第一次请求时响应头带上 ETag: abcd,之后的请求中带上 If-None-Match: abcd,服务器检查 ETag,返回 304 或 200。

    image

引用: 【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

Http 缓存机制作为 web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必须要掌握的知识,但最近我遇到了几个缓存头设置相关的题目,发现有好几道题答错了,有的甚至在知道了正确答案后依然不明白其原因,可谓相当的郁闷呢??!为了确认下是否只是自己理解不深,我特意请教了其他几位小伙伴,发现情况也或多或少和我类似。

为了不给大家卖关子,下面我贴出2道题,大家可以尝试解答下:

以下为 page.html 内容:

<!DOCTYPE html>
 http://www.w3.org/1999/xhtml">
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     <title>page页</title>
 </head>
 <body>
     <img src="images/head.png" />
     <a href="page.html">重新访问page页</a>
 </body>
 </html>

首次访问该页面,页面中 head.png 响应头信息如下:

HTTP/1.1 200 OK
 Cache-Control: no-cache
 Content-Type: image/png
 Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT
 Accept-Ranges: bytes
 Date: Thu, 10 Nov 2016 02:48:50 GMT
 Content-Length: 3534
  • 问题1:请问当点击“重新访问 page 页”链接重新加载该页面后, head.png 如何二次加载?

  • 问题2:如果将上述信息中的 Cache-Control 设置为 private,那么结果又会如何呢?

以上2道题,如果你能全部答对(哈哈,还请仔细确认下 why,以防歪打正着),那么恭喜你,你已对这些知识理解非常透彻了,我后面讲的内容你可以忽略,否则还请继续陪我往下唠唠吧!

首先回到开篇提到很多小伙伴(包括我)在解答 Http 缓存题目时栽跟头的问题,我觉得出现这种现象的根本原因在于我们吸收的知识还不够体系化,平时我们在学习这些知识时多半将其当作知识点来记,什么这个缓存头作什么、那个缓存头作什么用的,但实际中缓存头往往是多个之间相互配合协同工作的,有一套完整的工作体系。

今天我将按自己的理解,从系统体系化角度来讲讲 Http 缓存头是如何协同工作的(不正确的地方还请指正,但请不要喷我哦):

HTTP 缓存体系

首先我将 Http 缓存体系分为以下三个部分:

image

1. 缓存存储策略

用来确定 Http 响应内容是否可以被客户端缓存,以及可以被哪些客户端缓存

这个策略的作用只有一个,用于决定 Http 响应内容是否可缓存到客户端

对于 Cache-Control 头里的 Public、Private、no-cache、max-age 、no-store 他们都是用来指明响应内容是否可以被客户端存储的,其中前4个都会缓存文件数据(关于 no-cache 应理解为“不建议使用本地缓存”,其仍然会缓存数据到本地),后者 no-store 则不会在客户端缓存任何响应数据。另关于 no-cache 和 max-age 有点特别,我认为它是一种混合体,下面我会讲到。

通过 Cache-Control:Public 设置我们可以将 Http 响应数据存储到本地,但此时并不意味着后续浏览器会直接从缓存中读取数据并使用,为啥?因为它无法确定本地缓存的数据是否可用(可能已经失效),还必须借助一套鉴别机制来确认才行, 这就是我们下面要讲到的“缓存过期策略”。

2. 缓存过期策略

客户端用来确认存储在本地的缓存数据是否已过期,进而决定是否要发请求到服务端获取数据

这个策略的作用也只有一个,那就是决定客户端是否可直接从本地缓存数据中加载数据并展示(否则就发请求到服务端获?。?/strong>

刚上面我们已经阐述了数据缓存到了本地后还需要经过判断才能使用,那么浏览器通过什么条件来判断呢? 答案是:Expires,Expires 指名了缓存数据有效的绝对时间,告诉客户端到了这个时间点(比照客户端时间点)后本地缓存就作废了,在这个时间点内客户端可以认为缓存数据有效,可直接从缓存中加载展示。

不过 Http 缓存头设计并没有想象的那么规矩,像上面提到的 Cache-Control(这个头是在Http1.1里加进来的)头里的 no-cache 和 max-age 就是特例,它们既包含缓存存储策略也包含缓存过期策略,以 max-age 为例,他实际上相当于:

 Cache-Control:public/private(这里不太确定具体哪个)
 Expires:当前客户端时间 + maxAge 。

而 Cache-Control:no-cache 和 Cache-Control:max-age=0 (单位是秒)相当

这里需要注意的是:

  1. Cache-Control 中指定的缓存过期策略优先级高于 Expires,当它们同时存在的时候,后者会被覆盖掉。

  2. 缓存数据标记为已过期只是告诉客户端不能再直接从本地读取缓存了,需要再发一次请求到服务器去确认,并不等同于本地缓存数据从此就没用了,有些情况下即使过期了还是会被再次用到,具体下面会讲到。

3. 缓存对比策略

将缓存在客户端的数据标识发往服务端,服务端通过标识来判断客户端 缓存数据是否仍有效,进而决定是否要重发数据。

客户端检测到数据过期或浏览器刷新后,往往会重新发起一个 http 请求到服务器,服务器此时并不急于返回数据,而是看请求头有没有带标识( If-Modified-Since、If-None-Match)过来,如果判断标识仍然有效,则返回304告诉客户端取本地缓存数据来用即可(这里要注意的是你必须要在首次响应时输出相应的头信息(Last-Modified、ETags)到客户端)。至此我们就明白了上面所说的本地缓存数据即使被认为过期,并不等于数据从此就没用了的道理了。

关于 Last-Modified,这个响应头使用要注意,可能会影响到缓存过期策略,具体原因,后面我会通过解答开篇提到的2道题来作说明。

以上就是我所认识的缓存策略,下面我将缓存策略三要素和常用的几个缓存头(项)结合一起,让大家更清晰的认识到它们之间的关系:

image

通过上图我可以清晰的看到各缓存项分别属于哪个缓存策略范畴,这其中有部分重叠,它表明这些缓存项具有多重缓存策略,所以实际在分析缓存头的时候,除了常规的头外,我们还需要将这些具有双重缓存策略的项分解开来。

最后我们回到最开始提到的2道题目,我们来一起分解下:

第一道题:

 HTTP/1.1 200 OK
 Cache-Control: no-cache
 Content-Type: image/png
 Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT
 Accept-Ranges: bytes
 Date: Thu, 10 Nov 2016 02:48:50 GMT
 Content-Length: 3534

分析上述 Http 响应头发现有以下两项与缓存相关:

 Cache-Control: no-cache 
 Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT

我们上面讲到了 Cache-Control: no-cache 相当于 Cache-Control: max-age=0,且他们都是多重策略头,我们需将其分解:

Cache-Control: no-cache 等于 Cache-Control: max-age=0,接着 Cache-Control: max-age=0 又可分解成:

Cache-Control: public/private (不确定是二者中的哪一个)
 Expires: 当前时间

最终我们得到了以下完整的缓存策略三要素:

缓存策略三要素

所以最终结果是:浏览器会再次请求服务端,并携带上 Last-Modified 指定的时间去服务器对比:

  • a)对比失败:服务器返回200并重发数据,客户端接收到数据后展示,并刷新本地缓存。

  • b)对比成功:服务器返回304且不重发数据,客户端收到304状态码后从本地读取缓存数据。以下为模拟此种情况下请求后的抓包情况:

请求对比

这道题本身不难,但若认为 no-cache 不会缓存数据到本地,那么你理解起来就会很矛盾,因为如果文件数据没有被本地缓存,服务器返回304后将会无法展示出图片内容,但实际上它是能正常展示的。这道题很好的证明了 no-cache 也会缓存数据到本地这一说法。

第二道题:

 HTTP/1.1 200 OK
 Cache-Control: private
 Content-Type: image/png
 Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT
 Accept-Ranges: bytes
 Date: Thu, 10 Nov 2016 02:48:50 GMT
 Content-Length: 3534

解题思路和上题一样,首先先找到缓存相关项:

 Cache-Control: private 
 Last-Modified: Tue, 08 Nov 2016 06:59:00 GMT

这时我们会发现根本找不到缓存过期策略项,那答案会不会和上面一样? 一时半会也分析不出答案,那只能实际测试下了:

image

再看看 Chrome 浏览器下抓包:

image

可以看到,浏览器后续请求都直接取的本地缓存,看来的确存在某种缓存过期策略(根据我上面的缓存过期策略理论,浏览器如果直接从本地加载缓存数据,说明它相信本地缓存数据有效,那一定存在某种缓存过期判断条件)。这个问题百思不得其解,困扰了我好久,直到一次偶然的机会我在 Fiddler 响应信息面板里的 Caching 选项卡中找到了答案:

image

原来,在没有提供任何浏览器缓存过期策略的情况下,浏览器遵循一个启发式缓存过期策略:

根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的10%作为缓存时间周期。

贴一下Caching面板里的描述,英语好的同学可以精准翻译下:

 HTTP/1.1 Cache-Control Header is present: private
 HTTP Last-Modified Header is present: Tue, 08 Nov 2016 06:59:00 GMT
 No explicit HTTP Cache Lifetime information was provided.
 Heuristic expiration policies suggest defaulting to: 10% of the delta between Last-Modified and Date.
 That's '05:15:02' so this response will heuristically expire 2016/11/11 0:46:01.

最终我们得到了以下完整的缓存策略三要素:

image

最终结果

浏览器会根据 Date 和 Last-Modified 之间的时间差值缓存一段时间,这段时间内会直接使用本地缓存数据而不会再去请求服务器(强制请求除外),缓存过期后,会再次请求服务端,并携带上 Last-Modified 指定的时间去服务器对比并根据服务端的响应状态决定是否要从本地加载缓存数据。

缓存判断流程

缓存判断流程

demo验证

HTTP缓存大多是由Server端来控制,浏览器会根据第一次请求的响应头来设置后续请求的缓存策略。而且,Server端流行的Apache、Nginx等框架,都是默认或提供了HTTP缓存配置项。但为了更好的理解,我们也可以通过express、koa来自己手动验证下,这里我用koa对文中的html搭建了静态服务:

const koa = require('koa');
const app = new koa();

const serve = require('koa-static');
const conditional = require('koa-conditional-get');
const etag = require('koa-etag');

const path = require('path');

const staticPath = path.resolve(__dirname, 'static');

// 响应头添加etag
app.use(conditional());
app.use(etag());

// 设置静态服务
const staticServe = serve(staticPath, {
  setHeaders: (res, path, stats) => {
    if (path.indexOf('jpg') > -1) {
      res.setHeader('Cache-Control', ['private', 'max-age=60']);
    }
  }
});
app.use(staticServe);

app.listen(3200);
console.log('koa server is listening port 3200');

demo中,通过koa-static起了静态服务,同时为jpg后缀的文件设置了max-age-60。通过页面刷新,可以看到浏览器的请求在60s内和超过60s的不同情况。
通过koa-conditional-getkoa-etag,可以给请求的响应头添加ETag。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容

  • 网络特有的延迟以及数据传输的成本,制约互联网快速获取Web资源。为此,HTTP协议引入缓存以空间换时间,使浏览器缓...
    大头8086阅读 3,062评论 2 12
  • 本文内容大多参考《图解HTTP》一书 一. 认识代理服务器 所以讲缓存为什么要先扯代理服务器?别急,让我们看一下一...
    流光号船长阅读 1,916评论 0 10
  • 前端开发者丨http请求 https:www.rokub.com 前言见解有限, 如有描述不当之处, 请帮忙指出,...
    麋鹿_720a阅读 10,898评论 11 31
  • 全文转自HTTP 缓存 通过网络获取内容既速度缓慢又开销巨大。较大的响应需要在客户端与服务器之间进行多次往返通信,...
    Addy_Zhou阅读 346评论 0 0
  • 简介 web缓存大致可以分为:数据库缓存,服务器端缓存(代理服务器缓存,CDN缓存),浏览器缓存。浏览器缓存也包含...
    我向你奔阅读 492评论 0 1