速度、速度,还是速度,一个网站要想体验好,就必须在第一时间以最快的速度显示出来。mysql查询慢,就加一层 redis做缓存,网站资源加载慢,怎么做,使用 HTTP缓存。
HTTP缓存自 HTTP/1.0 就开始有,为的是减少服务器压力,加快网页响应速度。
缓存操作的目标
HTTP 缓存只能存储 GET 请求的响应,而对其他类型的请求无能为力。
缓存发展史
HTTP/1.0 提出缓存概念,即强缓存 Expires 和协商缓存 Last-Modified。后 HTTP/1.1 又有了更好的方案,即强缓存Cache-Control 和协商缓存 ETag。
为什么 Expires 和 Last-Modified 不适用呢?
Expires 即过期时间,但问题是这个时间点是服务器的时间,如果客户端的时间和服务器时间有差,就不准确。所以用 Cache-Control来代替,它表示过期时长,这就没歧义了。
Last-Modified即最后修改时间,而它能感知的单位时间是秒,也就是说如果在1秒内改变多次,内容文件虽然改变了,但展示还是之前的,存在不准确的场景,所以就有了ETag,通过内容给资源打标识来判断资源是否变化。
以下表格利于对比理解:
版本 强缓存 协商缓存 HTTP/1.0 Expires Last-Modified HTTP/1.1 Cache-Control ETag。
两大缓存类型对比
前文已介绍不同版本下的缓存类型。当时提了有一句强缓存和协商缓存,但没具体介绍。现在来讲讲这两种缓存类型。
强缓存
Cache-Control
Cache-Control VS Expires
协商缓存
协商缓存需要配合强缓存使用,使用协商缓存的前提是设置强缓存设置 Cache-Control: no-cache或者 pragma: no-cache或者max-age=0 告诉浏览器不走强缓存。
pragma 是 HTTP/1.0 中禁止网页缓存的字段,其取值为 no-cache 和 Cache-Control 的 no-cache效果一样。
ETag/If-None-Match
Last-Modified/If-Modified-Since
ETag VS Last-Modified
协商缓存的条件请求
前文说到协商缓存是在请求头添加 If-None-Match 或 If-Modified-Since,这些请求头是什么,添加有什么用?
强缓存是通过具体时间到期或过期时长来控制缓存,这就有个问题了,如果其中的一些文件修改了,因为强缓存,浏览器展示的还是原来的数据,所以对那种常变化的数据不能使用强缓存做缓存策略,于是乎,就有了协商缓存,通过文件变化告诉浏览器缓存失效,使用前需去服务器验证是否是最新版?
这样,浏览器就要连续发送两个请求来验证:
但这样的两个请求的网络成本太高,所以 HTTP 协议就定义了一系列 If开头的条件请求字段,专门用来检查验证资源是否过期,把两个请求合并在一个请求中做。而且验证的责任也交给服务器。
其中,最常见的当属是 If-Modified-Since 和 If-None-Match。它们分别对应Last-Modified 和ETag。需要第一次的响应报文预先提供 Last-Modified 和 ETag,然后第二次请求时就可以带上缓存里的原址,验证资源是否是最新的。
如果资源没有变,服务器就回应一个 304 Not Modified ,表示缓存依然有效,浏览器就可以更新一个有效期,然后使用缓存了。
缓存流程
什么时候用强缓存,什么时候用协商缓存?
首先强缓存的权重大于协商缓存,当强缓存存在时,协商缓存只能看着;其次 HTTP/1.1 中的缓存标识符大于 HTTP/1;所以当Cache-Control 存在时,看它的,如果它不存在,则看 Expires,如果将强缓存设置为Cache-Control:no-cache、Cache-Control:max-age=0、pragma:no-cache,即告诉浏览器不走强缓存,则进入协商缓存。
判断上次响应中是否有ETag,如果有,则发起请求,请求头中带有条件请求If-None-Match,如果没有,则再判断上次响应中是否有Last-Modified,如果有,则发起请求头中带If-Modified-Since的条件请求,如果没有,则说明没有协商缓存,发起 HTTP 请求即可。无论是带If-None-Match的请求还是 If-Modified-Since的请求,都会返回状态(由服务器端判读资源是否变化),如果是304,说明缓存资源未变,使用本地缓存;如果是200,则说明资源改变,发起 HTTP请求,并记住响应头中的 ETag/Last-Modified。
大致流程图如下所示:
缓存判断流程图
那么哪些资源要采用强缓存,哪些资源采用协商缓存呢?
像静态资源这类我们长期不会去变动的资源应该用强缓存,不难理解;而像我们常修改的文件应该采用协商缓存,如果资源没变,那么当用户第二次进去还是用该资源,如果资源修改,用户进入发起HTTP 请求获取最新资源。
我们在访问网站时,如果留心都能在 F12 中观察到一二。如图所示,我的五年前端三年面试放在 github 服务器上,F12进入Network中,能看到返回头中的信息。Cache-Control、Expires、ETag、Last-Modified都存在。
五年前端三年面试
缓存位置
上文中常提到无论使用强缓存还是协商缓存,都会从浏览器本地中获取,那么浏览器的本地存储是存在哪里,他们又有什么分类呢?
按照缓存位置分类,分为四处,Memory Cache(内存缓存)、Disk Cache(硬盘缓存)、Service Worker、PushCache。
Memory Cache
因为内存有限,并不是所有的资源文件都会放在内存里缓存,它主要用来缓存有 preloader 相关指令的资源,比如。preloader 可以一边解析 js/css 文件,一边网络请求下一个资源。
磁盘上的缓存。在所有浏览器缓存中,disk cache 覆盖面最大,它会根据 HTTP Header中的字段判断哪些资源需要缓存,哪些资源已经过期需要重新从服务器端请求。
Service Worker
独立线程,借鉴了 Web Worker 的思路。即让 JS运行在主线程之外,由于它脱离浏览器窗口,因为无法直接访问DOM,但是它还是能做很多事情,如
即推送缓存,浏览器中的最后一道防线,HTTP2中的内容。
优先级: Service Worker-->Memory Cache-->Disk Cache-->Push Cache。
实践
说了这么多理论知识,等实战的时候却一头雾水,怎么破?
以上皆为口舌之辩,唯有实践出真章(以上皆为面试之辩,唯有实践出本事)。
目前前端项目都是以 webpack 或类 webpack 工具库打包,在 webpack 中配置哈希,前端方面的缓存工作就完成了。
我们要实现的效果是:
webpack 中的哈希有三种:hash、chunkHash、contentHash。
这边需要把 CSS 用 contentHash 处理,其他资源用 chunkHash 做处理。
非前端工程化项目
即传统的前端页面,一般放在静态服务器中,那么就要对修改的文件做版本控制,例如在入口文件 index.js上加版本号(index-v2.min.js)或者加时间戳(time=1626226),以此做缓存策略。
后端缓存实践
真正起到缓存作用的是在后端,后端来设置缓存策略,告诉浏览器能否做缓存。这里我们对强缓存和协商缓存做个demo来实验下。
强缓存方案
代码如下:
= (); = (); = {: , // 禁用协商缓存: , // 禁用协商缓存: (, , ) => {(, ); // 强缓存超时时间为秒},};((( + ), ));();
强缓存效果
协商缓存方案
代码如下:
= (); = (); = {: , // 开启协商缓存: , // 开启协商缓存: (, , ) => {({: , // 浏览器不走强缓存: , // 浏览器不走强缓存});},};((( + ), ));();
效果如下:
协商缓存效果
总结
HTTP 为什么要缓存,为了分担服务器压力,也为了让页面加载更快。
有什么手段?HTTP 的强缓存和协商缓存,强缓存作用于那些不怎么变化的资源(如引入的库,js,css等),协商缓存适用常更新的文件(例如html)。
强缓存是什么?在 HTTP/1.0 中以 Expires 为依据,但它不准确,HTTP 协议升级成1.1后,用新标识符 Cache-Control来代替,但两者可以同时存在,Cache-Control 的权重更大一些。
协商缓存是什么?在 HTTP/1.0 中以 Last-Modified 为依据,即最后过期修改时间,它也不准确,HTTP升级成1.1后,用新标识符ETag 来代替,两者可同时存在,后者的权重更大。
无论是 Expires ,还是 Last-Modified,都是以时间点来依据,理论上是不出问题,但却出问题了,所以就有了新的方案。
其中强缓存存在时,浏览器会采用强缓存标识符来缓存,当将强缓存设置为失效时,浏览器则会采用协商缓存来做缓存策略。
以上,即使笔者所理解的 HTTP 缓存。
本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载者并注明出处:https://www.jmbhsh.com/shenghuozixun/33782.html