前端缓存-浏览器缓存

766 阅读9分钟

致敬达叔-谨以纪念重案组曹达华

浏览器缓存(http缓存),是指通过设置请求头和响应头,使浏览器对服务端最近请求的资源进行存储,通过这种方式来减少与服务端的交互,降低服务器的负担,提升响应速度,增强用户体验. 浏览器缓存分为强缓存和协商缓存.两者的区别主要在于使用缓存的时候,是否需要向服务端验证缓存是否可用

  1. 浏览器第一次向服务端请求资源,服务端按照请求进行处理,将对应资源返回给浏览器做响应
  2. 当浏览器再次向服务端请求该资源时,先判断这个资源是否命中强缓存,如果命中,直接从缓存中读取资源,而不需要向服务端请求.(强缓存)
  3. 如果没有命中强缓存,浏览器会发送一个请求到服务端,服务端根据请求的请求头(request header)判断是否命中协商缓存,如果命中,服务端会返回个304的状态码,告知浏览器可以从缓存中读取资源.(协商缓存)

强缓存

浏览器第一次向服务器请求资源时,服务器响应资源同时告诉浏览器把这个资源保存在本地,并且在有效期内如果再次请求这个资源,直接从本地获取,不需要向服务器请求.这种缓存方式被称为强缓存. 强缓存通过http的响应头的ExpiresCache-Control:max-age=xxx两个字段来控制.服务端通过这两个字段告诉浏览器资源的缓存过期时间和缓存的最大生命周期.浏览器根据缓存的的过期事件和缓存的最大生命周期,自行判断是否与服务器建立连接,直接从缓存读取资源.

Expires

http1.0规范,值为一个GMT格式的时间字符串如Sat, 27 Feb 2021 20:59:11 GMT,代表资源的过期时间,在这个时间之前,都可以认为这个资源是最新的,不需要从服务器获取.

  • 浏览器第一次请求的时候,会将响应的资源和响应头信息一块缓存起来
  • 再次请求时,会根据这个资源响应头的过期时间和当前时间做比较,即判断是否命中,如果命中就使用缓存,如果未命中就向服务端请求
  • 缺点:响应头上的expires是服务端上的时间,可能与浏览器存在偏差,造成缓存混乱

Cache-Control

http1.1规范,可以在请求头使用也可以在响应头使用.如max-age=3600,表示第一次获取资源后缓存的有效时间.相比于Expires,max-age是根据本地的时间计算资源的过期时间,更加可靠.当Cache-Control和Expires同时使用时,Cache-Control的优先级高于Expires.

  • 请求头Cache-Control
    • no-cache - 告知(代理)服务器不直接使用缓存,要求向原服务器发起请求
    • no-store - 所有内容都不会保存到缓存
    • max-age=xxx - 告知服务器返回一个存在时间不超过xxx秒的资源
    • max-stale=xxx - 表示客户端可以接受一个已经过期的资源,如果xxx设置,表示资源的过期时间不能超过xxx秒
    • min-fresh=xxx - 表示客户端希望接受一个xxx秒内更新的资源
    • no-transform - 表示客户端希望接受的资源没有被转换过,Content-Encoding、Content-Range、Content-Type等http头不能被修改
    • only-if-cached - 表示客户端只接受已缓存的响应
    • cache-extension - 自定义扩展
  • 响应头Cache-Control
    • public - 任何情况下都要缓存该资源
    • private=xxx - 表示响应只能被xxx缓存,其他用户不能缓存
    • no-cahce - 不能直接使用缓存,要向原服务器发起请求
    • no-store - 所有内容都不会保存到缓存中
    • no-transform - 表示客户端缓存文件时,不得对响应进行转换
    • only-if-cached - 告知代理服务器使用缓存
    • must-revalidate - 如果超过max-age时间,缓存失效,必须向原服务器请求进行重新验证
    • proxy-revalidate - 如果缓存失效,必须向代理服务器请求进行重新验证
    • max-age=xxx - 表示缓存内容将会在xxx秒后失效
    • s-maxage=xxx - 同max-age,但只对共享缓存有效(public)
    • cache-extension - 自定义扩展

max-age=0与no-cache不是等价的,max-age=0表示缓存在本窗口有效,关闭窗口缓存就失效,no-cache表示不能直接使用缓存(浏览器会进行缓存),需要向服务器发起请求.具体看浏览器区别.

协商缓存

协商缓存利用 Last-Modified + If-Modified-Since 和 Etag + If-None-Match 实现. Etag的精确度要高于Last-Modified,但是性能上Etag要低于Last-Modified. 服务端上 Etag + If-None-Match 优先级要高于 Last-Modified + If—Modified-Since.

Last-Modified + If-Modified-Since

If-Modified-Since 是请求头, Last-Modified 是响应头,通过比较这两个时间,服务器判断资源是否有过修改,如果没有修改,命中协商缓存,浏览器从缓存读取资源,如果修改了,服务器返回新的资源和新的Last-Modified:

  1. 浏览器第一次向服务器请求资源,服务器返回资源同时在响应头中添加Last-Modified,表示资源在服务器上的最后一次的修改时间,浏览器将资源和Last-Modified一起缓存
  2. 当再次请求时,会在请求头中添加If-Modified-Since = 上一次缓存的Last-Modified
  3. 服务器收到请求,比较资源的最后修改时间和If-Modified-Since,如果一致,命中协商缓存,返回304,浏览器从缓存中读取资源;如果不一致,服务器返回200 + 新的资源,响应头Last-Modified也进行更新.

有的时候呢,因为服务器上资源会出现周期性更新,资源修改时间改变了但是内容没有改变(脚本执行后台任务-),更新的信息无关紧要(添加注释),资源的更新频率小于1秒(Last-Modified无法精确到毫秒),所以这个时候采用Last-Modified+If-Modified-Since就不能准确的验证资源是否需要更新

Etag + If-None-Match

为解决上述问题,http同意用户对资源打上Etag(采用MD5等密码散列函数对资源编码获得标签或者通过版本号等方式)区分相同路径下的资源内容是否一致.If-None-Match 是请求头,Etag 是响应头,表示资源内容的唯一标示,随response返回:

  1. 浏览器第一次向服务器请求资源时,服务器返回资源同时在响应头中添加Etag,这个Etag是根据该资源生成的唯一标示,只要服务器认为资源有变化且应该提供新资源时,Etag就必须改变,浏览器将资源和Etag一起缓存
  2. 当再次请求时,会在请求头中添加If-None-Match = 上一次缓存的Etag
  3. 服务器根据请求的资源重新生成Etag,然后和If-None-Match对比,如果一致,命中协商缓存,返回304,浏览器从缓存中读取资源,如果不一致,服务器返回200 + 新的资源,同时将新的Etag返回

启发式缓存

如果响应头中未显示Expires和Cache-Control:max-age 或者 Cache-Control:s-maxage,并且响应中不包含其他有关缓存的限制,缓存可以采用启发式方法计算新鲜度寿命.通常会根据响应头中的2个时间字段Date减去Last-Modified值的10%作为缓存时间.

// Date:创建报文的日期
// Last-Modified:服务器声明文档的最后修改日期
response_is_fresh = max(0,(Date - Last-Modified))/10

缓存优先级

命中强缓存后,浏览器同样会显示状态码200的response,这时可以通过network下的size判断哪个是从服务端获取,哪个是从缓存获取的.这里就分为 memory cache(内存中的缓存)、disk cache(硬盘中的缓存).memory cache的优先级要高于disk cache.(先读内存再度硬盘)

几乎所有的网络请求资源都会被浏览器自动加入到memory cache中,但是因为请求资源的数量和浏览器的内存两个原因,memory cache只能作为短期存储,当前页面关闭清除memory cache.

  • memory cache: 将资源存储到内存中,下次访问时直接从内存中读取. <link rel="preloader" />或者<link rel="preload" />的资源必定会放入memory cache中.浏览器会在js和图片等文件解析完成(脚本、字体、图片)后存入到memory cache,刷新页面时直接从memory cache中读取(脚本随时可能执行),速度快,时间短
  • disk cache: 将资源(非脚本文件)存储到硬盘中,下次访问时直接从硬盘中读取. 持久存储,实际存在于文件系统中,它可以在相同的资源在跨会话甚至跨站点的情况下使用.disk cache会严格根据http头信息判断资源的缓存策略,当命中缓存时,从硬盘中获取资源(强缓存方式).浏览器会将css文件放入到disk cache,渲染页面时直接从disk cache中读取缓存(css加载一次就渲染出来了不需要频繁读取).
  • push cache(推送缓存):http/2的内容,它存在于会话(session)中,一旦会话结束就会被释放,并且缓存的时间也很短暂,但是不同的浏览器厂商支持的也不一样.push cache参考
    • 所有的资源都可以利用push cache缓存
    • 可以推送no-cache和no-store的资源
    • push cache是http缓存中的最后一级
    • 一旦连接关闭,会话结束,缓存就会被释放
    • 多个页面可以使用同一个http/2连接,那么也就可以使用同一个push cache
    • push cache 中的缓存只能被使用一次
    • 浏览器可以接受已经存在的资源推送
    • 跨域推送资源

在这些缓存之上,还存在一个service worker,具体可以参考上一篇文章,service worker使你可以通过JS优先使用缓存,如果service worker没有命中缓存,那么接下来才会是上面的缓存.即 service worker(JS) > memory cache > disk cache(强缓存) > push cache(http/2) > request(协商缓存).

浏览器触发

  • 地址栏回车/右键跳转 - 浏览器以最少的请求获取网页内容,使用强缓存
  • F5刷新/浏览器刷新按钮 - 浏览器使用协商缓存,不予许直接使用强缓存
  • CTRL + F5 - 直接请求,不使用缓存