上一篇文章我写了koa-static的源码解析,其中用到了HTTP的缓存策略,给返回的静态文件设置了一些缓存的头,比如Cache-Control之类的。于是我就跟朋友讨论了一下HTTP的缓存策略:

朋友说:“HTTP里面控制缓存的头(header)太多了,啥Cache-ControlETagLast-Modified,一大堆,乱七八糟的,而且之间逻辑关系不强,要掌握基本靠背!”

我有点惊讶:“为什么要去背这个呢?所有的技术都是为了解决问题而存在的,不了解问题而去单纯的学习技术,去,背,去,死记,确实很枯燥,而且效果不好。HTTP缓存策略只是为了解决客户端和服务端信息不对称的问题而存在的,客户端为了加快速度会缓存部分资源,但是下次请求时,客户端不知道这个资源有没有更新,服务端也不知道客户端缓存的是哪个版本,不知道该不该再返回资源,其实就是一个信息同步问题,HTTP缓存策略就是来解决这个问题的。如果我们跳出这种纯粹的技术思维,我们会发现生活中这种信息同步问题也很常见。而我们解决这些问题的思路很多时候都是司空见惯了,如果从这个角度来说,这个问题就很好理解!”

于是我给他讲了一个我小时候租光碟看奥特曼的故事。

租光碟看奥特曼

事情是这样的,我小时候特别喜欢看动画片,尤其是奥特曼,但是那时候没有电脑啊,也没有网络。我只有一台DVD播放机,于是我会经常跑去租光碟的店租奥特曼。

ETag

某天,我看完了《艾斯奥特曼》第10集,我还想继续看。于是我找到了光碟店的老板:“老板,第10集我看完了哦,你还有没有新的啊?”老板说:“有有有,刚出了第11集,你拿去吧!”

上面这一个简单的交流过程其实就包含了一个HTTP的缓存技术,那就是ETag!类比于网络请求,我其实就是客户端,光碟店就是服务端,我去租光碟就相当于发起一个请求。但是我去租光碟时,老板并不知道我看到哪集了,我们的信息是不同步的。所以我告诉了他一个标记(Tag),在这里这个标记就是第10集,老板拿到这个标记,跟他自己库存的标记比较一下,发现他最新标记是第11集,于是知道有更新了,将第11集给了我。

Last-Modified & If-Modified-Since

再来,我《艾斯奥特曼》看完了,我开始看《泰罗奥特曼》了。可是老板这次比较鸡贼,《泰罗奥特曼》没买正版的,是他自己翻录的,他翻录的时候自己也不知道是第几集,但是他聪明的在光盘上写上了翻录日期。于是我正在看的这盘也没啥封面,只光秃秃的写了一个2000年12月1日。当我这盘看完了,我又去找老板了:“老板,你这个2000年12月1日的我已经看完了,你还有没有新的啊?”这里的2000年12月1日其实就是标记了我手上副本的更新日期,这也对应了HTTP的一个缓存技术,那就是Last-ModifiedIf-Modified-Since。你可以理解为,老板给日期还取了一个名字,叫Last-Modified,所以光碟上完整文字是Last-Modified:2000年12月1日,而我去问的时候就这么问:“Do you have any updates IF-Modified-Since 2000年12月1日?”。

Expires和Max-Age

继续,我《泰罗奥特曼》也看完了,开始看《雷欧奥特曼》了。这《雷欧奥特曼》跟前面两个都不一样,我去租的时候老板就说了:“你小子别天天跑来问了!《雷欧奥特曼》我每周去进一次货,你每周一来拿就行!”这句话也对应了一个HTTP缓存技术,那就是ExpiresMax-Age。我知道了下周一之前,我手上都是最新的,到了下周一就过期(Expire)了。所以“我手上的是最新的”这个说法有个生命周期,他的年龄是有限的,他的年龄等于下周一更新时间减去当前时间,这就是他的最大年龄(Max-Age)。

Immutable

再来一个,我《雷欧奥特曼》也看完了,开始看《奈克斯特奥特曼》了。这《奈克斯特奥特曼》跟前面几个都不一样,我去租的时候老板说了:“小子,你这次运气好,这《奈克斯特奥特曼》已经出完了,你全部拿去吧,也不用天天跑来问了!”这句话对应的HTTP缓存技术是啥?当然是ImmutableImmutable就跟字面意思一样,不可变的!就像《奈克斯特奥特曼》一样,已经出完了,不用再去问更新了。

言归正传

扯蛋到这里结束,咱们言归正传!之所以举这么个例子,是为了说明HTTP缓存技术要解决的问题在生活中很常见,从这些常见的场景入手,理解起来更简单。下面我们正儿八经的来说说HTTP缓存技术:

两种机制

从上面的几个小例子可以看出,有时候为了知道是不是有更新,我必须去问老板,比如第一个例子里面:“老板,第10集我看完了哦,你还有没有新的啊?”。这种为了知道有没有更新,必须跟服务端沟通过才知道的,我们称之为协商缓存。还有些场景,我不去问就知道有没有更新,比如第三个例子,因为知道是周更的,当周一来之前,我都不会去问了,到了周一再去问,这种不用跟服务器协商直接用本地副本的叫做强制缓存。换成技术的话说就是,强制缓存不用发请求直接用本地缓存,协商缓存要发请求去问服务器有没有更新。下面我们详细来讲下这两种缓存:

协商缓存

前面第一个例子和第二个例子每次都需要向服务器端询问,所以是协商缓存

ETag和If-None-Match

ETag是URL的Entity Tag,就是一个URL资源的标识符,类似于文件的md5,计算方式也类似,当服务器返回时,可以根据返回内容计算一个hash值或者就是一个数字版本号,类似于我们的第10集,具体返回什么值要看服务器的计算策略。然后将它加到responseheader里面,可能长这样:

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

客户端拿到后会将这个ETag和返回值一起存下来,等下次请求时,使用配套的If-None-Match,将这个放到requestheader里面,可能长这样:

If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

然后服务端拿到请求里面的If-None-Match跟当前版本的ETag比较下:

  1. 如果是一样的话,直接返回304,语义为Not Modified,不返回内容(body),只返回header,告诉浏览器直接用缓存。
  2. 如果不一样的话,返回200和最新的内容

ETag配套的还有一个不太常用的request header ----If-Match,这个和前面If-None-Match的语义是相反的。前面If-None-Match的语义是如果不匹配就下载。而If-Match通常用于post或者put请求中,语义为如果匹配才提交,比如你在编辑一个商品,其他人也可能同时在编辑。当你提交编辑时,其他人可能已经先于你提交了,这时候服务端的ETag就已经变了,If-Match就不成立了,这时候服务端会给你返回412错误,也就是Precondition Failed,前提条件失败。如果If-Match成立,就正常返回200

Last-Modified & If-Modified-Since

Last-ModifiedIf-Modified-Since也是配套使用的,类似于ETagIf-None-Match的关系。只不过ETag放的是一个版本号或者hash值,Last-Modified放的是资源的最后修改时间。Last-Modified是放到responseheader里面的,可能长这样:

Last-Modified: Wed, 21 Oct 2000 07:28:00 GMT

而客户端浏览器在使用时,应该将配套的If-Modified-Since放到requestheader里面,长这样:

If-Modified-Since: Wed, 21 Oct 2000 07:28:00 GMT

服务端拿到这个头后,会跟当前版本的修改时间进行比较:

  1. 当前版本的修改时间比这个晚,也就是这个时间后又改过了,返回200和新的内容
  2. 当前版本的修改时间和这个一样,也就是没有更新,返回304,不返回内容,只返回头,客户端直接使用缓存

If-Modified-Since对应的还有If-Unmodified-SinceIf-Modified-Since可以理解为有更新才下载,那If-Unmodified-Since就是没有更新才下载。如果客户端传了If-Unmodified-Since,像这样:

If-Unmodified-Since: Wed, 21 Oct 2000 07:28:00 GMT

服务端拿到这个头后,也会跟当前版本的修改时间进行比较:

  1. 如果这个时间后没有更新,服务器返回200,并返回内容。
  2. 如果这个时间后有更新,其实就是这个if不成立,会返回错误代码412,语义为Precondition Failed

ETag和Last-Modified优先级

ETagLast-Modified都是协商缓存,都需要服务器进行计算和比较,那如果这两个都存在,用哪个呢?答案是ETagETag的优先级比Last-Modified。因为Last-Modified在设计上有个问题,那就是Last-Modified的精度只能到秒,如果一个资源频繁修改,在同一秒进行多次修改,你从Last-Modified上是看不出来区别的。但是ETag每次修改都会生成新的,所以他比Last-Modified精度高,更准确。但是ETag也不是完全没问题的,你的ETag如果设计为一个hash值,每次请求都要计算这个值,需要额外耗费服务器资源。具体使用哪一个,需要根据自己的项目情况来进行取舍。

强制缓存

上面扯蛋那里的第三个例子和第四个例子就是强制缓存,就是我知道在某个时间段完全不用去问服务端,直接去用缓存就行。这两个例子里面提到的Expires是一个单独的headermax-ageimmutable同属于Cache-Control这个header

Expires

Expires比较简单,就是服务器responseheader带上这个字段:

Expires: Wed, 21 Oct 2000 07:28:00 GMT

然后在这个时间前,客户端浏览器都不会再发起请求,而是直接用缓存资源。

Cache-Control

Cache-Control相对比较复杂,可设置属性也比较多,max-age只是其中一个属性,长这样:

Cache-Control: max-age=20000

这表示当前资源在20000秒内都不用再请求了,直接使用缓存。

上面提到的immutable也是Cache-Control的一个属性,但是是个实验性质的,各个浏览器兼容并不好。设置了Cache-control: immutable表示这辈子都用缓存了,再请求是不可能的了。

其他常用属性还有:

no-cache:使用缓存前,强制要求把请求提交给服务器进行验证(协商缓存验证)。

no-store:不存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。

另外Cache-Control还有很多属性,大家可以参考MDN的文档

Expires和Cache-Control的优先级

就一句话:如果在Cache-Control响应头设置了 max-age 或者 s-maxage 指令,那么 Expires 头会被忽略。

协商缓存和强制缓存优先级

这个其实很好理解,协商缓存需要发请求跟服务器协商,强制缓存如果生效,根本就不会发请求。所以这个优先级就是:先判断强制缓存,如果强制缓存生效,直接使用缓存;如果强制缓存失效,再发请求跟服务器协商,看要不要使用缓存

总结

本文从生活中常见的场景入手,阐述了HTTP缓存机制其实是提高访问速度和解决信息不同步的一种机制。这种信息不同步在生活中很常见,很多解决思路我们已经司空见惯,带着这种思维,我们可以很好的理解HTTP缓存机制。HTTP缓存机制要点如下:

  1. HTTP缓存机制分为强制缓存协商缓存两类。
  2. 强制缓存的意思就是不要问了(不发起请求),直接用缓存吧。
  3. 强制缓存常见技术有ExpiresCache-Control
  4. Expires的值是一个时间,表示这个时间前缓存都有效,都不需要发起请求。
  5. Cache-Control有很多属性值,常用属性max-age设置了缓存有效的时间长度,单位为,这个时间没到,都不用发起请求。
  6. immutable也是Cache-Control的一个属性,表示这个资源这辈子都不用再请求了,但是他兼容性不好,Cache-Control其他属性可以参考MDN的文档
  7. Cache-Controlmax-age优先级比Expires高。
  8. 协商缓存常见技术有ETagLast-Modified
  9. ETag其实就是给资源算一个hash值或者版本号,对应的常用request headerIf-None-Match
  10. Last-Modified其实就是加上资源修改的时间,对应的常用request headerIf-Modified-Since,精度为
  11. ETag每次修改都会改变,而Last-Modified的精度只到,所以ETag更准确,优先级更高,但是需要计算,所以服务端开销更大。
  12. 强制缓存协商缓存都存在的情况下,先判断强制缓存是否生效,如果生效,不用发起请求,直接用缓存。如果强制缓存不生效再发起请求判断协商缓存

参考资料:

ETag MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/ETag

Last-Modified MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Last-Modified

Expires MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Expires

Cache-Control MDN文档:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control

文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。

作者博文GitHub项目地址: https://github.com/dennis-jiang/Front-End-Knowledges

作者文章汇总:https://juejin.im/post/5e3ffc85518825494e2772fd

我也搞了个公众号[进击的大前端],可以第一时间获取高质量原创,欢迎关注~

轻松理解HTTP缓存策略的更多相关文章

  1. 【转】理解Java Integer的缓存策略

    本文将介绍 Java 中 Integer 缓存的相关知识.这是 Java 5 中引入的一个有助于节省内存.提高性能的特性.首先看一个使用 Integer 的示例代码,展示了 Integer 的缓存行为 ...

  2. 理解Java Integer的缓存策略

    转载自http://www.importnew.com/18884.html 本文将介绍 Java 中 Integer 缓存的相关知识.这是 Java 5 中引入的一个有助于节省内存.提高性能的特性. ...

  3. 理解Java Integer的缓存策略【转】

    本文由 ImportNew - 挖坑的张师傅 翻译自 javapapers.欢迎加入翻译小组.转载请见文末要求. 本文将介绍 Java 中 Integer 缓存的相关知识.这是 Java 5 中引入的 ...

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

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李 ...

  5. Redis的缓存策略和主键失效机制

    作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略. >>EXPIRE主键失效机制 在Redis当中,有生存期的key被称为volatile,在创建缓存时,要为给定的key设置 ...

  6. Http协议:彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

    转载:http://mp.weixin.qq.com/s/uWPls0qrqJKHkHfNLmaenQ 导语 Http 缓存机制作为 web 性能优化的重要手段,对从事 Web 开发的小伙伴们来说是必 ...

  7. Hibernate(四)——缓存策略+lazy

    Hibernate作为和数据库数据打交道的框架,自然会设计到操作数据的效率问题,而对于一些频繁操作的数据,缓存策略就是提高其性能一种重要手段,而Hibernate框架是支持缓存的,而且支持一级和二级两 ...

  8. RDD概念、特性、缓存策略与容错

    一.RDD概念与特性 1. RDD的概念 RDD(Resilient Distributed Dataset),是指弹性分布式数据集.数据集:Spark中的编程是基于RDD的,将原始数据加载到内存变成 ...

  9. LruCache的缓存策略

    一.Android中的缓存策略 一般来说,缓存策略主要包含缓存的添加.获取和删除这三类操作.如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大 ...

随机推荐

  1. guitar pro系列教程(十三):Guitar Pro教程之打谱使用技巧

    前面我们有讲过关于{cms_selflink page='index' text='Guitar Pro'}在声音方面的一些使用技巧,Guitar Pro在打谱,试听,伴奏方面对于刚学吉他作谱的朋友们 ...

  2. centos8 yum 升级nginx

    原文地址:https://blog.csdn.net/lpwmm/article/details/105627476 CentOS8的Yum仓库中内置的nginx版本是1.14.1,最近漏扫提示需要升 ...

  3. nginx介绍及常用功能

    什么是nginx nginx跟Apache一样,是一个web服务器(网站服务器),通过HTTP协议提供各种网络服务. Apache:重量级的,不支持高并发的服务器.在Apache上运行数以万计的并发访 ...

  4. Codeforces Round #670 (Div. 2) D. Three Sequences 题解(差分+思维+构造)

    题目链接 题目大意 给你一个长为n的数组a,要你构造一个非严格单调上升的数组b和一个非严格单调下降的数组c,使得\(b_i+c_i=a_i\) 要你使这两个数组b,c中最大的元素最小,还有q次修改(q ...

  5. 一口气带你读懂80年IT发展史

    计算机的发展历史有多长?真正意义上的计算机诞生,距今也只有80多年的时间.80年,对于每一个人来说,是很长的时间,但对于整个历史来说,只是短短的一瞬间.这八十多年只是整段历史中的一粒尘埃罢了,但却对这 ...

  6. MySQL 当前时间,今日时间,前日时间 详解

    MySQL 获取当前日期及日期格式 获取系统日期: NOW() 格式化日期: DATE_FORMAT(date, format) 注: date:时间字段 format:日期格式 返回系统日期,输出 ...

  7. Spring 源码学习 04:初始化容器与 DefaultListableBeanFactory

    前言 在前一篇文章:创建 IoC 容器的几种方式中,介绍了四种方式,这里以 AnnotationConfigApplicationContext 为例,跟进代码,看看 IoC 的启动流程. 入口 从 ...

  8. 这 6 个 Spring Boot 项目够经典

    不得不佩服 SpringBoot 的生态如此强大,今天我给大家推荐几款 Gitee 上优秀的后台管理系统,小伙伴们再也不用从头到尾撸一个项目了. SmartAdmin 我们开源一套漂亮的代码和一套整洁 ...

  9. MySQL二进制文件(binlog)

    二进制文件(binlog)记录对MySQL数据库执行更改的所有操作,但不包括SELECT和SHOW这类操作,因为这类操作没有改变数据. 为什么会有binlog? 首先 binlog 是 Server ...

  10. 20190814_tomcat配置项目的错误页

    1. 打开项目中的web.xml, 注意不是tomcat的web.xml; 一般是在项目的 WEB-INF目录下, 然后加上下面的语句 <error-page> <error-cod ...