缓存是一种web性能提升手段,通过复用以前获取的资源,既缓解了服务端压力,又减少了客户端获取资源的时间。
缓存的种类很多,可以大致分为两类:
- 共享缓存:其存储的响应可以被多个用户使用,如代理缓存;
- 私有缓存:只能用于单独用户,如浏览器缓存;
本文主要介绍浏览器和代理缓存(只能缓存get响应),除此之外还有网关缓存、CDN、反向代理缓存和负载均衡器等部署在服务器上的缓存方式,为站点和web应用提供更好的稳定性、性能和扩展性。
一、缓存位置:
- Service Worker:是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用Service Worker必须使用https作为传输协议;
- Memory Cache:内存中的缓存,主要包含的是当前页面中已经获取到的资源,例如页面中已经下载的样式、脚本、图片等。读取速度快,但是一旦关闭tab页面,内存中的缓存就被释放了;
- Disk Cache:硬盘中的缓存,读取速度慢,但是容量比内存缓存大,而且能够保存很久;
- Push Cache:推送缓存,是使用http/2由服务器主动推送给客户端的缓存,当以上三种缓存都没有命中的时候,它才会被使用。它只在session中存在,一旦会话结束就被释放,并且缓存时间也很短,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行http头部的缓存指令;
在浏览器读取缓存时,会依次检查上述缓存是否命中,如果都没有命中则重新发送请求。
二、http缓存体系:
http缓存体系分为以下三个部分:
- 缓存存储策略:用于决定http响应内容是否可以缓存到客户端;
- 缓存过期策略:用于决定客户端是否可直接从本地缓存中加载数据并展示(否则就要发请求到服务端获取);
- 缓存对比策略:用于比较客户端缓存和服务器资源是否一样,来确定客户端缓存是否仍然有效;
三、强缓存和协商缓存:
强缓存:
也叫本地缓存,本地保存有要请求到数据,并且没有过期,则浏览器不会发送真实的http请求,而是直接从本地读取,并且返回200状态码。在Chrome中,强缓存又分为Disk Cache(存放在硬盘中)和Memory Cache(存放在内存中),存放的位置由浏览器控制。是否使用强缓存(过期)由Expires、Cache-Control、Pragma三个header属性来控制。
-
Expires是http/1.0的规范,它的值是一个绝对时间的GMT格式的时间字符串。如:expires:Fri, 14 Apr 2017 10:47:02 GMT。这个时间代表了强缓存的失效时间。但这种方式有个明显的缺点,由于失效的时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。如果同时出现Cacha-Control:max-age=xxx和Expires,那么max-age的优先级会更高。 -
Cache-Control是在http/1.1中出现的,主要是利用该字段的max-age值来判断强缓存是否过期,它是一个相对时间,例如Cache-Control:max-age=3600表示该资源的有效期是3600秒。还有下面几个比较常用的设置值:no-cache:强制不命中强缓存,直接与服务器判断是否协商缓存;no-store:直接禁止浏览器缓存数据,每次用户请求该资源时,都会向服务器发送一个请求,获取完整资源数据;public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器;private:只能被终端用户的浏览器缓存,不允许CDN等中间缓存服务器对其进行缓存;must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证;
-
Pragma是在http/1.0标准中定义的一个header属性,只有一个属性值,就是no-cache,效果与Cache-Control中的no-cache相同,但是http的响应头没有明确定义这个属性,所以它不能拿来完全代替http/1.1中定义的Cache-Control头。通常定义Pragma以向后兼容基于http/1.0的客户端。
注: 在没有提供任何浏览器缓存策略的情况下,客户端计算响应头中两个字段:Date和Last-Modified之间的时间差值(单位:秒),取该值的10%作为缓存过期的周期。
协商缓存:
也叫弱缓存,如果没有命中强缓存(普通刷新或者缓存过期),就会发送一恶搞请求到服务器,验证协商缓存是否命中(本地缓存是否可用),如果协商缓存命中,请求响应返回304状态码并且会显示一个NotModified的字符串。
协商缓存主要用到两组header属性:
Etag和If-None-Match(强校验器);Last-Modified和If-Modified-Since(弱校验器);
使用协商缓存的过程:
- 在第一次请求时,服务器会将资源最后修改的时间通过头部字段
Last-Modified发送给客户端,以及一个唯一标记Etag,强缓存资源拥有这两个属性; - 如果未命中强缓存,浏览器会向服务器发送请求:
- 客户端会通过头部字段
If-None-Match将强缓存资源的Etag发送给服务端; - 客户端还会通过头部字段
If-Modified-Since将强缓存资源的Last-Modified时间戳发送给服务端;
- 客户端会通过头部字段
- 服务端会:
- 对比客户端发送过来的Etag和本地的Etag是否相同,不同说明资源更新了;
- 检测资源是否在Last-Modified时间戳之后更新;
- 资源未更新,则返回304状态码,使用协商缓存,否则返回200状态码,服务器重新将更新后的资源发送给客户端;
Etag的优先级高于Last-Modified
为什么要有Etag:
- 一些文件也许周期性更改,但是它的内容并不改变(仅仅改变修改时间),这个时候我们并希望客户端认为这个文件被修改了,而重新向服务器请求完整数据;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,
If-Modified-Since能检查到的粒度是s级的,这种修改无法判断; - 某些服务器不能精确得到文件的最后修改时间;
四、用户操作对缓存的影响:
- 地址栏输入url回车、页面链接跳转、新开窗口、历史栏前进后退等操作都会先查询强缓存,再查询协商缓存;
- 普通刷新会跳过强缓存,直接查询协商缓存;
- 强制刷新会跳过强缓存和协商缓存,直接重新从服务器拉取资源;
参考资料:
MDN HTTP 缓存
HTTP缓存和浏览器的本地存储
彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法
图解 HTTP 缓存