共计 3627 个字符,预计需要花费 10 分钟才能阅读完成。
缓存的概念分很多种,本次讨论的主要就是前端缓存中的 Http 缓存。
缓存是怎么回事
前端发送请求主要经历以下三个过程,请求 -> 处理 -> 响应。如果有多次请求就需要重复执行这个过程。
重复请求的过程
以下是一个重复请求的流程图:
从以上的流程图可以看书,如果用户重复请求同一资源的话,会对服务器资源造成浪费,服务器重复读取资源,发送给浏览器后浏览器重复下载,造成不必要的等待与消耗。
缓存读取的过程
缓存读取就是浏览器在向服务器请求资源之前,先查询一下本地缓存中是否存在需要的资源,如果存在,那便优先从缓存中读取。当缓存不存在或者过期,再向服务器发送请求。
如何开启 Http 缓存并对缓存进行设置,是本次讨论的关键。
缓存的类型
浏览器有如下常见的几个字段:
expires: 设置缓存过期的时间
private: 客户端可以缓存
public: 客户端和代理服务器都可缓存
max-age=xxx: 缓存的内容将在 xxx 秒后失效
no-cache: 需要使用对比缓存来验证缓存数据
no-store: 所有内容都不会缓存,强制缓存,对比缓存都不会触发
last-modified: 内容上次被修改的时间
Etag: 文件的特殊标识
强制缓存和协商缓存
缓存方法可以分为强制缓存与协商缓存。
从字面理解,强制缓存的方式简单粗暴,给 cache 设置了过期时间,超过这个时间之后 cache 过期需要重新请求。上述字段中的 expires 与 cache-control 中的 max-age 都属于强制缓存。
协商缓存根据一系列条件来判断是否可以使用缓存。
强制缓存优先级高于协商缓存
强制缓存
expires
expires 给浏览器设置了一个绝对时间,当浏览器时间超过这个绝对时间之后,重新向服务器发送请求。
Expires: Fri, 04 Jan 2019 12:00:00 GMT
这个方法简单直接,直接设定一个绝对的时间 (当前时间 + 缓存时间)。但是也存在隐患,例如浏览器当前时间是可以进行更改的,更改之后 expires 设置的绝对时间相对不准确,cache 可能会出现长久不过期或者很快就过期的情况。
cache-control: max-age
为了解决 expires 存在的问题,Http1.1 版本中提出了 cache-control: max-age,该字段与 expires 的缓存思路相同,都是设置了一个过期时间,不同的是 max-age 设置的是相对缓存时间开始往后多久,因此不存在受日期不准确情况的影响。
但是强制缓存存在一个问题,该缓存方式优先级高,如果在过期时间内缓存的资源在服务器上更新了,客服端不能及时获取最新的资源。
协商缓存
协商缓存解决了无法及时获取更新资源的问题。以下两组字段,都可以对资源做标识,由服务器做分析,如果未进行更新,那返回 304 状态码,从缓存中读取资源,否则重新请求资源。
last-modify
last-modify 告知了客户端上次修改该资源的时间,
Last-Modified: Wed, 02 Jan 2019 03:06:03 GMT
浏览器将这个值记录在 if-modify-since 中 (浏览器自动记录了该字段信息),下一次请求相同资源时,与服务器返回的 last-modify 进行比对,如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。
last-modify 以秒为单位进行更新,如果小于该单位高频进行更新的话,不适合采用该方法。
ETag
ETag 是对资源的特殊标识
Etag: W/”e563df87b65299122770e0a84ada084f”
请求该资源成功之后,将返回的 ETag 存入 if-none-match 字段中 (浏览器自动记录了该字段信息),同样在请求资源时传递给服务器,服务器查询该编码对应的资源有无更新,无更新返回 304 状态,更新返回 200 并重新请求。
以下有个小例子,查询书籍更新:
当书籍信息查询之后,再次查询,服务器根据资源的 ETag 查询得知该资源没有进行更新,返回 304 状态码。
更新返回的数据信息,再次查询,返回 200 状态码,重新进行请求:
从返回的 Request Headers 可以看出,再次请求时,浏览器自动发送了 If-Modified-Since 与 If-None-Match 两个字段,浏览器根据这两个字段中(If-None-Match 优先级大于 If-Modified-Since)来判断是否修改了资源。
ETag 如何计算
ETag 是针对某个文件的特殊标识,服务器默认采用 SHA256 算法生成。也可以采用其他方式,保证编码的唯一性即可。
缓存的优先级
根据上文优缺点的比对,可以得出以下的优先级顺序:
Cache-Control > Expires > ETag > Last-Modified
如果资源需要用到强制缓存,Cache-Control 相对更加安全,协商缓存中利用 ETag 查询更新更加全面。
图片来源: 浏览器缓存机制详解
缓存存储在哪
disk cache
disk cache 为存储在硬盘中的缓存,存储在硬盘中的资源相对稳定,不会随着 tab 或浏览器的关闭而消失,可以用来存储大型的,需长久使用的资源。
当硬盘中的资源被加载时,内存中也存储了该资源,当下次改资源被调用时,会优先从 memory cache 中读取,加快资源的获取。
memory cache
memory cache 即存储在内存中的缓存,内存中的内容会随着 tab 的关闭而释放。
当接口状态返回 304 时,资源默认存储在 memory cache 中,当页面关闭后,重新打开需要再次请求。
这两种存储方式的区别可以参考该回答
When you visit a URL in Chrome, the HTML and the other assets(like images) on the page are stored locally in a memory and a disk cache. Chrome will use the memory cache first because it is much faster, but it will also store the page in a disk cache in case you quit your browser or it crashes, because the disk cache is persistent. 当您访问 chrome 中的 URL 时,页面上的 HTML 和其他资产(如图像)将本地存储在内存和磁盘缓存中。Chrome 将首先使用内存缓存,因为它的速度快得多,但它也会将页面存储在磁盘缓存中,以防您退出浏览器或它崩溃,因为磁盘缓存是持久的。
为什么有的资源一会 from disk cache,一会 from memory cache
三级缓存原理
先去内存看,如果有,直接加载
如果内存没有,择取硬盘获取,如果有直接加载
如果硬盘也没有,那么就进行网络请求
加载到的资源缓存到硬盘和内存,下次请求可以快速从内存中获取到
为什么有的请求状态码返回 200,有的返回 304
200 from memory cache
不访问服务器,直接读缓存,从内存中读取缓存。此时的数据时缓存到内存中的,当关闭进程后,也就是浏览器关闭以后,数据将不存在。
但是这种方式只能缓存派生资源。
200 from disk cache
不访问服务器,直接读缓存,从磁盘中读取缓存,当关闭进程时,数据还是存在。
这种方式也只能缓存派生资源
304 Not Modified
访问服务器,发现数据没有更新,服务器返回此状态码。然后从缓存中读取数据。
薄荷应用
举一个简单的小????,以薄荷的减肥群页面为讨论对象,查看一下资源加载的情况:
这些图片都是从硬盘中读取,因为没有在内存中获取到响应的资源,当我们刷新页面时,这个资源因为从硬盘中读取时,也存储到了内存中,再次获取就是从内存中获取了:
当我们没有关闭页面时,内存中的资源始终存在,重新打开则内存释放。
CDN 缓存
CDN 边缘节点缓存策略因服务商不同而不同,但一般都会遵循 http 标准协议,通过 http 响应头中的 Cache-control: max-age 的字段来设置 CDN 边缘节点数据缓存时间。
当客户端向 CDN 节点请求数据时,CDN 节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,CDN 节点就会向源站发出回源请求,从源站拉取最新数据,更新本地缓存,并将最新数据返回给客户端。
如何合理应用缓存
强制缓存优先级最高,并且资源的改动在缓存有效期内都不会对缓存产生影响,因此该方法适用于大型且不易修改的的资源文件,例如第三方 CSS、JS 文件或图片资源,文件后可以加上 hash 进行版本的区分。建议将此类大型资源存入 disk cache,因为存在硬盘中的文件资源不易丢失。
协商缓存灵活性高,适用于数据的缓存,根据上述方法的对比,采用 Etag 标识进行对比灵活度最高,并考虑将数据存入内存中,因为内存加载速最快,并且数据体积小,不会占用大量内存资源。
广而告之
本文发布于薄荷前端周刊,欢迎 Watch & Star ★,转载请注明出处。
欢迎讨论,点个赞再走吧 。◕‿◕。 ~