说起缓存,每个前端开发者都不会陌生。它是很常见的前端性能优化手段之一,无论在节省带宽、提高加载和渲染速度、减少网络阻塞,以及提高用户体验上,都发挥着很重要的作用。缓存过程
页面的加载,可能会经历如上图所示的缓存过程。之所以会说可能,是因为有些缓存在一次请求中,不会经历。比如:如果请求命中了强缓存,那浏览器就直接返回结果了,不会进入协商缓存流程。下面我们会详细讲述下这些缓存的作用以及区别。DNS缓存
通常,我们在访问一个网站时,会先在浏览器地址栏中输入一串URL,这个URL的格式大概是这个样子的:协议://域名或IP地址:端口/路径?参数
。从URL中,我们可以看到域名
和IP地址
都可以帮我们定位到指定的服务主机上。IP地址
就是每一台物理机的唯一逻辑地址,可以很快找到对应主机,但是域名
就不同了,它和IP地址
是映射关系,它可以对应一个或多个IP地址
。那我们想要通过域名
查找到对应的服务器,就需要先找到它映射的IP地址
是哪个,再跟进这个IP地址
找到服务器。域名
查找IP地址
的过程会对网络请求带来一定的损耗,所以浏览器在第一次获取到IP地址
后,会将其缓存起来。下次相同域名再次发起请求时,浏览器会先查找本地缓存,如果缓存有效,则会直接返回该IP地址
,否则会继续开始寻址之旅。memory cache
本着操作系统先读内存,再读硬盘的常理,浏览器会先从memory cache
中读取缓存,再去disk cache
中读取。由于memory cache
是短期存储,在浏览器tab关闭后,缓存便会失效。当数据量过大,即使tab不关闭,缓存依然会失效。
那哪些资源会存入memory cache
中呢?答:几乎所有资源。
看上面这张图的资源加载,全部通过preload
的方式加载资源,在第一次加载时,全部存储到了disk cache
中,如下图:
而在我刷新页面后(f5刷新),script
资源会存储到了memory cache
中,而stylesheet
依然存储在disk cache
中。
另外,prefetch
加载的资源,通常也会放入disk cache
。disk cache
硬盘缓存又叫HTTP缓存,它也是浏览器缓存中最重要的内容。
disk cache是永久缓存,根据HTTP协议头的缓存相关字段来判断缓存是否有效。它又分为两种:强缓存
和协商缓存
。HTTP缓存的详细过程如下图:
浏览器发起请求时,首先检测有没有缓存,若没有缓存,直接向服务端发起请求,若有,则会遵循HTTP缓存规则,在命中强缓存或协商缓存时,会将缓存结果返回,不然则从服务端获取数据。强缓存
对于强缓存,控制它的字段分别是:Expires
和Cache-Control
,其中Cache-Control
优先级比Expires
高。
- Expires
Expires
是HTTP1.0
控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,形如:Mon, 23 Nov 2020 08:39:58 GMT
,即再次发起该请求时,如果客户端的时间小于Expires
的值时,直接使用缓存结果。Expires
控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存则会直接失效,这样的话强制缓存的存在则毫无意义。
- Cache-Control
基于上述Expires
的缺陷,在HTTP1.1
中,用Cache-Control
替代了Expires
。当然,为了兼容HTTP1.0
和HTTP1.1
,在实际应用中,这两个值都会设置。Cache-Control
的指令分为:缓存请求指令
和缓存响应指令
,值分别如下:
- 缓存请求指令
Cache-Control | 含义 |
- 缓存响应指令
Cache-Control | 含义 |
协商缓存
从前面介绍的内容,我们知道,浏览器在发起请求时,会先判断是否命中强缓存,如果没有命中,则会去验证协商缓存是否被命中。协商缓存有两对字段,他们的验证方法分别如下:
- Last-Modified & If-Modified-Since
- 服务器通过
Last-Modified
字段,将资源最后一次修改的时间告知客户端 - 浏览器将
Last-Modified
和资源内容一起缓存起来 - 在浏览器下一次请求相同资源时,会从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的
Last-Modified
的值写入到请求头的If-Modified-Since
字段。 - 此时,服务器会将
If-Modified-Since
的值与Last-Modified
字段进行对比。如果相等,则表示未修改,响应304,不会返回任何内容,告知客户端可以使用缓存;反之,则表示修改了,响应200,并返回数据。 - 浏览器会将新的内容和
Last-Modified
缓存起来。
- ETag & If-None-Match
如果资源在1s内有多次修改的话,Last-Modified
是监听不到的,因为它的缓存时间是以s为过期时间单位的。对于这种情况,HTTP1.1
引入了以hash形式记录资源内容是否修改的ETag
来更准确的判断是否命中缓存。ETag
的验证方法与Last-Modified
是一致的,只是ETag
是以hash生成的特殊标识。服务端会将浏览器请求头中的If-None-Match
与ETag
进行比较,若相等,则命中缓存,返回304;否则返回200和最新的资源内容。ETag
的优先级是高于Last-Modified
的,为了兼容不同版本的协议,通常项目中,都会把两者都设置。CDN缓存
CDN作为一种服务端缓存方案,在我们平时的工作中,并不陌生。CDN旨在解决的最重要的问题就是网络延迟,CDN服务商会将源站的资源缓存到遍布全国的高性能加速节点上,当用户访问相应的业务资源时,用户会被调度至最接近的节点最近的节点IP返回给用户,在web性能优化中,它主要起到了,缓解源站压力,优化不同用户的访问速度与体验的作用。
前面我们了解到,每次的资源请求发起时,浏览器都会检测本地是否有缓存并且未过期,如果是,则直接使用缓存,否则则会向服务端发起请求。如果此时在浏览器和服务器之间增加一层CDN服务,整个访问过程会是什么样的呢?
如上图所示,客户端浏览器先检查是否有本地缓存是否过期,如果过期,则向CDN边缘节点发起请求,CDN边缘节点会检测用户请求数据的缓存是否过期,如果没有过期,则直接响应用户请求,此时一个完成http请求结束;如果数据已经过期,那么CDN还需要向源站发出回源请求(back to the source request),来拉取最新的数据。
可以看出,CDN的优点很明显了:
- CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
- 大部分请求在CDN边缘节点完成,CDN起到了分流作用,减轻了源站的负载。
总结
以上讲述了普通业务中,一次请求经历的直接相关缓存内容。还有其他的缓存,比如Service Worker
,Nginx缓存等。有兴趣的同学可以自己去了解下,有很多挺不错的分享文章。参考文章
[1].一文读懂前端缓存 -
[2].Cache Control MDN - https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
[3].A Tale of Four Cache - https://calendar.perfplanet.com/2016/a-tale-of-four-caches/
[4].浏览器缓存机制剖析- http://louiszhai.github.io/2017/04/07/http-cache/
[5].一文读懂前端缓存(超详细) - https://www.jianshu.com/p/227cee9c8d15?from=singlemessage