对HTTP缓存的全面理解

896 阅读6分钟

缓存是一种web性能提升手段,通过复用以前获取的资源,既缓解了服务端压力,又减少了客户端获取资源的时间。

缓存的种类很多,可以大致分为两类:

  • 共享缓存:其存储的响应可以被多个用户使用,如代理缓存;
  • 私有缓存:只能用于单独用户,如浏览器缓存;

本文主要介绍浏览器和代理缓存(只能缓存get响应),除此之外还有网关缓存、CDN、反向代理缓存和负载均衡器等部署在服务器上的缓存方式,为站点和web应用提供更好的稳定性、性能和扩展性。

一、缓存位置:

缓存位置.png

  1. Service Worker:是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用Service Worker必须使用https作为传输协议;
  2. Memory Cache:内存中的缓存,主要包含的是当前页面中已经获取到的资源,例如页面中已经下载的样式、脚本、图片等。读取速度快,但是一旦关闭tab页面,内存中的缓存就被释放了;
  3. Disk Cache:硬盘中的缓存,读取速度慢,但是容量比内存缓存大,而且能够保存很久;
  4. Push Cache:推送缓存,是使用http/2由服务器主动推送给客户端的缓存,当以上三种缓存都没有命中的时候,它才会被使用。它只在session中存在,一旦会话结束就被释放,并且缓存时间也很短,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行http头部的缓存指令;

在浏览器读取缓存时,会依次检查上述缓存是否命中,如果都没有命中则重新发送请求。

二、http缓存体系:

http缓存体系分为以下三个部分:

  • 缓存存储策略:用于决定http响应内容是否可以缓存到客户端;
  • 缓存过期策略:用于决定客户端是否可直接从本地缓存中加载数据并展示(否则就要发请求到服务端获取);
  • 缓存对比策略:用于比较客户端缓存和服务器资源是否一样,来确定客户端缓存是否仍然有效;

三、强缓存和协商缓存:

强缓存:

也叫本地缓存,本地保存有要请求到数据,并且没有过期,则浏览器不会发送真实的http请求,而是直接从本地读取,并且返回200状态码。在Chrome中,强缓存又分为Disk Cache(存放在硬盘中)和Memory Cache(存放在内存中),存放的位置由浏览器控制。是否使用强缓存(过期)由ExpiresCache-ControlPragma三个header属性来控制。

  • Expires是http/1.0的规范,它的值是一个绝对时间的GMT格式的时间字符串。如:expires:Fri, 14 Apr 2017 10:47:02 GMT。这个时间代表了强缓存的失效时间。但这种方式有个明显的缺点,由于失效的时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。如果同时出现Cacha-Control:max-age=xxxExpires,那么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的客户端。

注: 在没有提供任何浏览器缓存策略的情况下,客户端计算响应头中两个字段:DateLast-Modified之间的时间差值(单位:秒),取该值的10%作为缓存过期的周期。

协商缓存:

也叫弱缓存,如果没有命中强缓存(普通刷新或者缓存过期),就会发送一恶搞请求到服务器,验证协商缓存是否命中(本地缓存是否可用),如果协商缓存命中,请求响应返回304状态码并且会显示一个NotModified的字符串。
协商缓存主要用到两组header属性:

  • EtagIf-None-Match(强校验器);
  • Last-ModifiedIf-Modified-Since(弱校验器);

使用协商缓存的过程:

  1. 在第一次请求时,服务器会将资源最后修改的时间通过头部字段Last-Modified发送给客户端,以及一个唯一标记Etag,强缓存资源拥有这两个属性;
  2. 如果未命中强缓存,浏览器会向服务器发送请求:
    • 客户端会通过头部字段If-None-Match将强缓存资源的Etag发送给服务端;
    • 客户端还会通过头部字段If-Modified-Since将强缓存资源的Last-Modified时间戳发送给服务端;
  3. 服务端会:
    • 对比客户端发送过来的Etag和本地的Etag是否相同,不同说明资源更新了;
    • 检测资源是否在Last-Modified时间戳之后更新;
    • 资源未更新,则返回304状态码,使用协商缓存,否则返回200状态码,服务器重新将更新后的资源发送给客户端;

Etag的优先级高于Last-Modified

为什么要有Etag

  1. 一些文件也许周期性更改,但是它的内容并不改变(仅仅改变修改时间),这个时候我们并希望客户端认为这个文件被修改了,而重新向服务器请求完整数据;
  2. 某些文件修改非常频繁,比如在秒以下的时间内进行修改,If-Modified-Since能检查到的粒度是s级的,这种修改无法判断;
  3. 某些服务器不能精确得到文件的最后修改时间;

http缓存 (1).png

四、用户操作对缓存的影响:

  • 地址栏输入url回车、页面链接跳转、新开窗口、历史栏前进后退等操作都会先查询强缓存,再查询协商缓存;
  • 普通刷新会跳过强缓存,直接查询协商缓存;
  • 强制刷新会跳过强缓存和协商缓存,直接重新从服务器拉取资源;

参考资料:

MDN HTTP 缓存
HTTP缓存和浏览器的本地存储
彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法
图解 HTTP 缓存