详解HTTP的缓存机制与原理

概述

缓存的重要性不言而喻,通过网络请求资源缓慢并且降低了客户端的用户体验,增添了服务端的负担。很多短期之内不会经常发生变化的资源文件没必要每次访问都想服务端进行数据请求,而缓存策略的使用就是为了改善客户端的呈现时间,降低服务端的负担。

对于HTTP的缓存机制来说,策略体现在HTTP的头部信息的字段上,而这些策略根据是否需要重新向服务器端发起请求可以分为强缓存协商缓存两大类。接下来用UML时序图的形式来呈现这两大类缓存策略的大体过程。

Tips: Vscode 配合插件 plantUML画(写)UML图很爽。相比之前用的ProcessOn拖拽的形式,你只需要熟悉plantUML的语法,在你的电脑上安装一下javaGraphviz 的环境,不用操心样式的展现UML真心舒服。下面的图我就是用这个工具去画的,很推荐。

  • 强缓存
image

强缓存紧密联系着一个缓存时间期限,当浏览器请求资源的时候会查看缓存中的资源是否存在并且确定该缓存的资源是否过了“保质期”,若没有超过保质期则将取得缓存中的资源进行下一步处理

  • 协商缓存
image

可见协商缓存无论如何都会和服务器交互,比较强缓存稍微要复杂一点,但是二者是相辅相成并且可以共同存在的,强缓存优先级较高,意味着请求一个资源时会先比较强缓存的字段,如果命中则不会再执行接下来的协商缓存的过程。

接下来就是要介绍,两类缓存相关HTTP header相关字段的控制实现了。

细节

1.强缓存

与强缓存相关的HTTP header 的字段有两个 Expires以及Cache-Control

  • Expires

expires 字段规定了缓存的资源的过期时间,在此时间之前,缓存中的资源都是有效的,该字段的 value 是一个格林威治时间格式(GMT)的时间,即世界标准时间,js 通过 new Date().toUTCString()可得到,形如 Tue, 27 Feb 2018 06:37:48 GMT。他的缺点很明显,时间期限是服务器生成,存在着客户端和服务器的时间误差,固定时间,HTTP 1.0时的规范。相比较接下来介绍的cache-control优先级较低。

  • cache-control

该字段的值(默认为private):

image

其中最常用的值max-age单位为秒,对比expires体现着一个相对时间,即多少秒后这个强缓存机制下的缓存资源失效。

publicprivate区别在于是否有中间商赚差价(是否允许CDN代理服务器缓存)

需要注意的一点是该字段值定义为no-cache并不是说,不准使用缓存,而是需要走接下来的优先级相对较低的另一类--协商缓存。真正决定不用缓存内的资源是将该值定义为no-store

2.协商缓存

协商缓存是通过客户端和服务端进行HTTP通信时,所在响应头和请求头中互相表达“暧昧”的,相互通气,互送缓存标识。

  • Last-Modified 和 If-Modified-Since

第一次请求某一个资源时,由于一定不会走缓存,所以服务器端会在资源的响应头中加上一个形如Last-Modified:Mon, 26 Feb 2018 06:37:41 GMT的字段告诉客户端浏览器,这个资源上次最后修改的时间;刷新页面再次请求,这时候的协商缓存会在请求头中加上一个形如If-Modified-Since:Mon, 26 Feb 2018 06:37:41 GMT,字面翻译就是,是否在上个“暧昧”时间后修改了,值毫无疑问是服务端上一次响应给他的时间,让服务器去判断是否在此时间之后资源内容发生了变化

整个过程也很简单,最后的结果也很简短。如果服务端发现改变了资源,就伴着200的 statuscode 和新鲜的资源给到客户端,若是没有修改,304 Not Modified让客户端从缓存中取。

  • Etag 和 If-None-Match

同样,第一次客户端请求一个资源文件时,服务端随资源在响应头部中甩来一个字段 Etag ,形如ETag:W/"1823823287"该字段的值是该资源在服务器端的唯一标识,生成的Etag值的策略有服务端决定,总之是资源的一个唯一的标识。资源发生变化则该值也发生变化。下一次客户端请求同一个资源的时候,在请求头将这次得到的值放在请求头中一个叫 If-None-Match 的字段中甩给服务端。

整个过程也很简单,最后的结果也很简短。如果服务端发现改变了资源,就伴着200的 statuscode 和新鲜的资源给到客户端,若是没有修改,304 Not Modified让客户端从缓存中取。

上述两个方式中,Etag 和 If-None-Match的优先级要高于Last-Modified 和 If-Modified-Since,进而会衍生出一个思考,二者相比功能相同,但是表达形式决定了 Etag 解决了 Last-Modified 存在的一些问题,比如Last-Modified 是比较时间,精确到秒,若是毫秒级的改变则没法兼顾,存在着周期性更改的资源,然而有可能资源本身的内容并没有改变,那如果重新请求响应意义并不是那么的大。所以不难理解Etag具有高优先级有他的合理之处。

简单操练一下

使用Node的Express框架能够轻松的观察到这些字段带来的影响实验。接下来通过使用Express4.x依旧保留下来的中间件express.static搭建一个建议的静态文件响应服务器。

image

其中 public 文件夹里面放着一个主页面和两张不一样的图片用来改变

//app.js
const express = require('express')
const path = require('path')

const app = express()

app.use(express.static(path.join(__dirname, "public"), {
    etag: false,
    lastModified:false, 
    cacheControl: false,  
    setHeaders:function(res,path,stat) {
        res.set({
            expires: new Date(Date.now() + 60000),
        })
    }
}))

app.listen(3000, () => {
    console.log('App listening on port 3000!');
});
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h1>Hello World</h1>
    <img src="./stream.jpg">
</body>
</html>

详细的express.static的细节请参考文档,这里不过多赘述。

之前我们提到过,expires这个选项是优先级相比较其他的控制选项,所以想要种 expires 观察效果是记得向上述app.js一样把其他的都关掉,因为他们都是默认为 true的。

然后启动服务器,访问3000端口,打开开发人员工具,你就会看到如下被种了expires的响应头了。

image

之后,你改变图片的名称,调换两张图片的名字,你就会发现在 expires 的时间到之前图片都不会发生变化,并且可见,他是从缓存中取得

image

接下来,改变app.js

app.use(express.static(path.join(__dirname, "public"), {
    etag: false,
    maxAge:30000,
    lastModified:false,   
    setHeaders:function(res,path,stat) {
        res.set({
            expires: new Date(Date.now() + 600000),
        })
    }
}))

maxAge的单位是毫秒,此时由于优先级的关系,覆盖掉 expires 之后在30秒之内交换图片名称,重新刷新浏览器将不会看到资源发生变化,知道maxAge的时间到。对应的响应头如下:

image

继续修改app.js:

app.use(express.static(path.join(__dirname, "public"), {
    etag: false,
    maxAge:30000,
    cacheControl: false,
    lastModified:true,   
}))

你需要删掉对强缓存的设置,因为强缓存的设置会比协商缓存被先执行,同样的操作你将看到接下来的响应头和请求头。

image
image

随后,你将上述优先级最高的 Etag 字段改为 true,得到的响应头和请求头信息如下:

image
image

此外你也会发现如下情况:

image
image

这种现象就解释了,协商缓存虽然总是要访问服务器,但是当资源没有变动的时候,服务端只会返回带着304状态码的很小的一个头部,并不会像第一次携带文件资源那么大了。

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

推荐阅读更多精彩内容