http缓存详解,http缓存推荐方案
前言
通过本文,你将了解到http缓存机制是怎样的,no-cache到底有没有缓存,地址栏回车,F5,ctrl+F5的区别,以及当下较为推荐的缓存方案等。
自从和前端组的同事一起整了个前端扫盲计划,想着给他们讲点什么,花了一周时间整理了http缓存的知识,花了一个多小时也算帮同事们重新理了理这玩意。本文借用了部分优秀文章的思路,这里就当一个整理分享了。
那么我们开始~
说明
阅读前提:http缓存主要针如css,js,图片等更新频率不大的静态文件。
首先得先明确这个概念,不然以下的概念以及例子,都会显得不太有意义,或者不是那么完美。
一、http缓存请求相应头
阅读本文,先大概了解这些缓存字段是必须的,后面也会细说,先留个印象。
1.Cache-Control
请求/响应头,缓存控制字段,可以说是控制http缓存的最高指令,要不要缓存也是它说了算。
它有以下常用值
1.1 no-store:所有内容都不缓存
1.2 no-cache:缓存,但是浏览器使用缓存前,都会请求服务器判断缓存资源是否是最新,它是个比较高贵的存在,因为它只用不过期的缓存。
1.3 max-age=x(单位秒) 请求缓存后的X秒不再发起请求,属于http1.1属性,与下方Expires(http1.0属性)类似,但优先级要比Expires高。
1.4 s-maxage=x(单位秒) 代理服务器请求源站缓存后的X秒不再发起请求,只对CDN缓存有效(这个在后面会细说)
1.5 public 客户端和代理服务器(CDN)都可缓存
1.6 private 只有客户端可以缓存
2.Expires
响应头,代表资源过期时间,由服务器返回提供,GMT格式日期,是http1.0的属性,在与max-age(http1.1)共存的情况下,优先级要低。
3.Last-Modified
响应头,资源最新修改时间,由服务器告诉浏览器。
4.if-Modified-Since
请求头,资源最新修改时间,由浏览器告诉服务器(其实就是上次服务器给的Last-Modified,请求又还给服务器对比),和Last-Modified是一对,它两会进行对比。
5.Etag
响应头,资源标识,由服务器告诉浏览器。
6.if-None-Match
请求头,缓存资源标识,由浏览器告诉服务器(其实就是上次服务器给的Etag),和Etag是一对,它两会进行对比。
二、为什么要使用HTTP缓存
假设我们请求一次服务器,请求头大小1kb,响应头大小1kb,请求文件10kb。
1次请求流量:12kb
10次请求流量:120kb
N次请求:12*N....
这只是假想的一次请求,但事实上的请求不仅是请求文件,请求客户端也会更多,那么问题就很明显:
1.客户端每次都要请求服务器,浪费流量(比如手机端?)。
2.服务器每次都得提供查找,下载,请求用户基础如果较大,服务器存在较大压力。
3.客户端每次请求完都要进行页面渲染,用户体验较差。
那么我们是否可以将请求的文件存放起来使用,比如使用http缓存。
三、使用http缓存
1.让服务器与浏览器约定一个文件过期时间——Expires(GMT时间格式)。
日常请求对话
第一次请求
浏览器:服务器服务器,我现在需要一个a.js文件,帮我找找,然后给我。
服务器:次次找我要,烦不烦,文件给你可以,我们约定个时间(Expires),时间没到别来烦我了,返回了a.js以及过期时间Expires。
后续请求.....
浏览器会先对比当前时间是否已经大于Expires,也就是判断文件是否超过了约定的过期时间。
时间没过,不发起请求,直接使用本地缓存。
时间过期,发起请求,继续上述的浏览器与服务器的谈话日常。
问题:假设Expires已过期,浏览器再次请求服务器,但a.js相比上次并未做任何改变,那这次请求我们是否通过某种方式加以避免?
2.让服务器与浏览器在约定文件过期时间的基础上,再加一个文件最新修改时间的对比——Last-Modified与if-Modified-Since
日常请求对话
第一次请求
浏览器:服务器服务器,我现在需要一个a.js,你找到了给我,顺便给我个过期时间,时间没到我保证不烦你!
服务器:瓜娃子,行,过期时间我给你,另外再给你一个文件最新修改时间Last-Modified,到时候文件过期了咱两核对文件修改时间,对得上你就别烦我,返回a.js+Expires+Last-Modified。
后续请求....
Expires未过期,浏览器机智的使用本地缓存,免得挨打。
Expires过期,服务器带上了文件最新修改时间if-Modified-Since(也就是上次请求服务器返回的Last-Modified),服务器将if-Modified-Since与Last-Modified做了个对比。
if-Modified-Since 与Last-Modified不相等,服务器查找了最新的a.js,同时再次返回Expires与全新的Last-Modified
if-Modified-Since 与Last-Modified相等,服务器返回了状态码304,文件没修改过,你还是用你的本地缓存。
如下图,请求头与响应头文件修改时间相同,所以返回了304,使用本地缓存:
问题:浏览器端可以随意修改Expires,Expires不稳定,Last-Modified只能精确到秒,假设文件是在1s内发生变动,Last-Modified无法感知到变化,这种情况下浏览器永远拿不到最新的文件(假想极端情况)。
3.让服务器与浏览器在过期时间Expires+Last-Modified的基础上,再增加一个文件内容唯一对比标记——Etag与If-None-Match。哦对了,我们说Expires不稳定,这里我们再加入一个max-age来加以代替(cache-control其中一个值)。
日常对话
第一次请求
浏览器:服务器服务器,你懂得~~~~~~
服务器:我不懂!a.js我给你,过期时间我也给你,再给你一个max-age=60(单位秒),Last-Modified你也给我收好,再加一个文件内容唯一标识符Etag。
后续请求....
60S内,不发起请求,直接使用本地缓存。(max-age=60代表请求成功缓存后的60S内不再发起请求,与Expires相似,同时存在max-age优先级要比Expires高,区别后面具体说)
60S后,浏览器带上了if-Modified-Since 与If-None-Match(上次服务器返回来的Etag)发起请求,服务器对比If-None-Match与Etag(不对比if-Modified-Since与Last-Modified了,Etag优先级比Last-Modified高,毕竟更精准)
If-None-Match与Etag不相等,说明a.js内容被修改过,服务器返回最新a.js与全新的Etag与max-age=60与Last-Modified与Expires
If-None-Match与Etag相等,说明a.js文件内容无任何改变,返回304,告诉浏览器继续使用之前的本地缓存。
如下图,服务器Etag与If-None-Match相同,所以返回了状态码304,由于优先级问题,虽然也有if-Modified-Since与Last-Modified,但这里不会对两者做对比。
问题:我们已经可以精确的对比服务器文件与本地缓存文件差异,但其实上面方案的演变都存在一个较大缺陷, max-age或Expires不过期,浏览器无法主动感知服务器文件变化。
四、http缓存方案
1.md5/hash缓存
通过不缓存html,为静态文件添加MD5或者hash标识,解决浏览器无法跳过缓存过期时间主动感知文件变化的问题。
为什么这么做?实现原理是什么?
我们前面说的http缓存方案,服务器与浏览器的文件修改时间对比,文件内容标识对比,前提基础都是建立在两者文件路径完全相同的情况下。
module/js/a-hash1.js与module/js/a-hash2.js是两个完全不同的文件,假想浏览器第一次加载页面,请求并缓存了module/js/a-hash1.js,第二次加载,文件指向变成了module/js/a-hash2.js,浏览器会直接重新请求a-hash2.js,因为这就是两个完全不同的文件,哪里还有什么http缓存文件对比,t通过这种做法,我们就可以从根本上解决过期时间没到浏览器无法主动请求服务器的问题。因此我们只需要在项目每次发布迭代将修改过的静态文件添加不同的MD5或hash标识就好啦。
注意,这里不推荐缓存html文件(或许有更好的做法,欢迎留言),这样每次html加载渲染都可以感知文件变化,反正文件没变还是使用本地缓存,文件名都变了说明修改过,重新请求缓存就好了。
怎么改?一个个手动去修改?那不得累死。webpack提供了webpack-md5-hash插件,可以帮助开发者在项目发布时自动修改文件标识。
我们公司因为用的是fis3打包工具,这里使用的是fis3构建-文件指纹(搜文件指纹),原理都差不多。
2.CDN缓存(作为了解)
在文章开头cache-control相关值介绍中,提到了例如s-maxage代理服务器的概念,本人在整理http缓存相关知识点时,从同学口中了解到了也较为推荐的http缓存方案——CDN缓存,这里就作为一个拓展吧,正常的缓存还是推荐MD5缓存。
2.1什么是CDN
了解CDN缓存,先得知道什么是CDN,CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术(较为官方的说明)。
之前看到一个不错的例子,这里直接拿过来举例说说CDN。
假设多年前我们所在的城市只有一个火车站,每次春运,整个城市的人都得去这个火车站买票,人流量以及购票的需求可想而知有多大,为了缓解这个问题,城市的不同区,都出现了火车票代售点,这样每个区的人都可以就近买票了,火车站总站的压力就这样大大减轻了。
我们可以把每个区的售票点称之为CDN节点,也就是前面所说的代理服务器。简而言之,我们可以把CDN理解成浏览器与服务器之间的临时站点,它会替服务器处理一部分的浏览器请求,从而整理减轻总服务器的压力。
我们可以把CDN的价值归纳为:
1.CDN通过分流的形式,大大减轻源站的访问压力。
2.就像住的区比较偏远,每次买票要去城市中心,而这个区后来有了分站,火车票就可以就近购买一样。CDN也解决了跨地区访问问题,根本上为访问提供了加速。
2.2什么是CDN缓存
CDN边缘节点缓存数据,当浏览器请求,CDN将代替源站判断并处理此处请求。
日常请求对话
第一次请求
浏览器:服务器老哥,我需要a.js.
服务器:(恼羞成怒)文件我给我小弟CDN了,以后你要这个找CDN,别找我了。成功返回a.js给CDN,CDN进行缓存,同时CDN返回给浏览器,浏览器自己也进行了缓存(cache-control的值public就是用在这的)。
后续请求...
浏览器:服务器,我缓存时间到了,赶紧给我对比下文件,看看要不要重新返回给我。
CDN节点:打住打住,叫唤啥呢,我大哥比较忙,文件给我看看,请求被代理了。
情况1:CDN节点自己缓存的文件未过期,于是返回了304给浏览器,打回了这次请求。
情况2:CDN节点发现自己缓存的文件过期了,为了保险起见,自己发起请求给了服务器(源站),成功拿回了最新数据,然后再交给与了浏览器。
其实说到这,CDN缓存的问题也跟前面的http缓存一样,CDN缓存时间不过期,浏览器始终被拦截,无法拿到最新的文件。
但是我们回归http缓存问题本质,缓存本身针对于更新频率不高的静态文件,其次,CDN缓存提供了分流以及访问加速其它优势条件。这里我问过同学,得到的信息是,CDN类似一个平台,是可以通过登录,手动更新CDN缓存的,变相解决了浏览器缓存无法手动控制的问题。
那么两种http缓存方案就说到这里了,接下来谈谈http缓存其它的一些问题和概念。
五、浏览器地址栏回车,新开窗口,F5刷新,CTRL+F5刷新等浏览器操作对HTTP缓存的影响
在利用fiddler抓包看不同网站缓存时,发现了一个有趣的问题,大部分网站的cache-control都设置的为no-cache。
我在前面说了,no-cache不是不缓存,要缓存,但是浏览器在协商性缓存情况下,都会无条件像服务器发起请求,判断自己的缓存是不是最新,如果是就接着用,不是就请求最新的文件,缓存起来用,以此循环。
那么有必要设置过期时间Expires与max-age吗?有!
当我们第一次浏览一个页面,关闭后,第二次再打开还是属于新开窗口行为,如果设置了缓存时间,新开窗口会走强缓存,可以避免反复的文件下载,加快页面渲染,提升用户体验性。
(其实一开始我是觉得Expires与max-age是不需要设置的,直到我使用pageSpeed对公司网站做了个性能评分,发现还是推荐使用缓存时间,结合上面的话,其实是有道理的)
对于上面的话不理解没关系,这里结合百度百科与其它博客的概念,我们对浏览器的不同行为对缓存的影响做一个总结。
1.浏览器地址栏回车,或者点击跳转按钮,前进,后退,新开窗口,这些行为,会让Expires,max-age生效,也就是说,这几种操作下,浏览器会判断过期时间,再考虑要不要发起请求,当然Last-Modified和Etag也有效。
2.F5刷新浏览器,或者使用浏览器导航栏的刷新按钮,这几种,会忽略掉Expires,max-age的限制,强行发起请求,Last-Modified和Etag在这种情况下也有效。
3.CTRL+F5是强制请求,所有缓存文件都不使用,全部重新请求下载,因此Expires,max-age,Last-Modified和Etag全部失效。
但事实上,我们很少用到地址栏回车,地址栏跳转,所以要触发缓存时间的判断,还需要特定的操作,站在我的理解,综合考虑下,才有了这么多网站的cache-control设置为no-cache,也就是使用缓存前都判断文件是否为最新,更为合理。
6.强缓存与协商性缓存(弱缓存)
了解了上面不同浏览器行为对http缓存的不同影响,理解强缓存与协商性缓存就很容易了。
强缓存:不发起http请求,直接使用本地缓存,比如浏览器地址栏回车,使用浏览器的刷新按钮,在Expires或max-age生效的情况下,触发的都是强缓存。
协商性缓存(弱缓存):在使用本地缓存前,先与服务器协商,核对缓存文件是否为最新。比如设置了cache-control=no-cache,不管你做任何操作,都会发起请求,这一类就是协商性缓存了。
7.max-age和Expires的区别
在用fiddler抓包的时候,发现不少网站同时设置了max-age和Expires,为毛要设置两个,功能不都差不多吗,两者区别是啥?
1.max-age是http1.1的属性,Expires是http1.0的属性,为了做到向下兼容,一般写两个。但如在1.1环境下,max-age优先级比Expires高。
2.max-age是相对过期时间,Expires是绝对过期时间。max-age在浏览器成功缓存文件后,只需相对请求成功之后的多长时间不再发起请求就好了,而Expires总是需要服务器返回一个精准的GMT格式的日期,并以这个日期为标准来判断缓存是否过期,相对就比较麻烦,所以才有了max-age这样的存在来代替它。
同理,no-cache和 Pargma也是这样的存在,一个是1.1的属性,一个是1.0,向下兼容,同时写了两个。
好了,说到这里其实介绍的差不多了,了解了这些,浏览器和服务器之间也能过上幸福的生活了。
对了,上面抓包工具是fiddler,别找中文版了,不存在的,直接下,官方也有使用说明文档,这里就不多说了。
参考资料:
2.CDN缓存那些事