前端123:浏览器缓存的工作方式

Frontend in depth: The browser cache

nojsja 2021-01-29
字数:2.8k丨 阅读时间:10 分钟

>> 博客原文

浏览器缓存的工作流程


     通过网络获取内容既速度缓慢又开销巨大。较大的响应需要在客户端与服务器之间进行多次往返通信,这会延迟浏览器获得和处理内容的时间,还会增加访问者的流量费用。因此,缓存并重复利用之前获取的资源的能力成为性能优化的一个关键方面。

     这里先看张大家最熟悉的Devtools网络图:

图中青色、绿色和橙色圈出的部分分别是来自内存(memory缓存)、磁盘(disk缓存)和Http请求拿到的数据(非缓存),还有一种返回码304的请求也是从缓存(memory/disk)中获取数据。304跟memory/disk缓存的区别是:在浏览器判断资源已经过期的情况下会去服务器查询资源是否更新,如果资源没更新则返回304码,浏览器收到304码就会更新资源的过期时间并直接从之前disk/memory缓存中拿到当前资源,换言之如果资源没过期,那么浏览器就会跳过向服务器校验资源这一步并直接去拿memory/disk缓存获取。

大致流程如下:

  • 1)首先检查是否存在 Service Worker Cache,没命中或不存在则进行下一步

  • 2)检查内存中是否存在资源,存在的话直接加载(from memory - 200)。

  • 3)如果内存没有,择取从硬盘获取,存在且没过期的话直接加载(from disk - 200),过期了直接向服务器发送请求获取资源。如果资源没更新,服务器返回304,浏览器从硬盘缓存中获取资源,并更新过期时间/Etag/Last-Modified。如果资源更新了则获取最新的资源,并通过HTTP请求将资源返回,重新缓存资源并更新过期时间/Etag/Last-Modified

  • 4)如果硬盘也没有,那么会向后端发送HTTP网络请求。

  • 5)加载到的资源缓存到硬盘和内存,并更新资源的过期时间/Etag/Last-Modified

     Service Worker Cache具有较高的优先级,数据控制更为复杂,操作自由度最高;Memory Cache更多的强调了一种缓存存储方式和浏览器内存缓存策略;HTTP Cache相对于Memory Cache根据存储方式的不同也能叫做Disk Cache,它依赖于整个HTTP缓存校验流程(强缓存和协商缓存),并通过校验来最终确定何时从缓存读取,何时从服务器更新资源;Push Cache资料较少,应用得不多,暂时只做介绍。

Service-Worker Cache(优先级最高)


     Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

     Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

     当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

Memory Cache(优先级次之)


     Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经获取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。内存缓存在缓存资源时并不关心返回资源的HTTP响应头部 Cache-Control 是什么值,换句话说这是一种强依赖于浏览器本地内存管理策略的缓存方式,各个浏览器对内存缓存的处理方式也略有区别。

     Memory Cache遵循这些策略:

  • 对于大文件来说,大概率是不存储在内存中的,反之优先
  • 当前系统内存使用率高的话,文件优先存储进硬盘

* HTTP Cache(优先级次之)


     HTTP缓存根据工作方式分为强缓存协商缓存,浏览器首先会判断强缓存是否命中,命中失败才会尝试进行协商缓存

1)强缓存

  • > HTTP 1.0时代 - expires
         我们通过浏览器获取服务器远程资源时,服务器通过http请求response headers返回一个expires时间戳字段(上图中蓝色部分),例如expires: Wed, 13 Oct 2021 22:15:05 GMT,表明这个资源的过期时间为格林威治时间2021年10月13日 周三 22:15:05(北京时间+8h=格林威治时间),浏览器判断当前时间在资源过期时间之前的话,就会从缓存中去读取资源(如果缓存中存在的话),否则会重新向服务器发送请求。
         expires的工作机制要求客户端时间与服务器时间误差较小,否则缓存更新策略可能在短时间不生效。

  • > HTTP 1.1时代 - cache-control
         cache-control: max-age方式也是通过服务器返回资源时携带的response headers中的相应字段实现的,比如:cache-control: max-age=31536000,表明资源距浏览器接收到此资源后的31536000秒后过期。与expires返回的时间戳方式不同,cache-control为了避免时间误差,直接返回一个时间长度,浏览器可以根据一个本地时间差值进行精确判断。
    cache-control其它相关字段还有:
         i. public/private:在依赖各种代理的大型架构中,我们不得不考虑代理服务器的缓存问题,public 与 private 用来控制代理服务缓存是否能缓存资源。如果我们为资源设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存;如果我们设置了 private,则该资源只能被浏览器缓存。private 为默认值,不过在只设置s-maxage的情况下,代理缓存也能生效。
         ii. s-maxage:针对于代理服务器的缓存问题,此字段用于表示 cache 服务器上(比如 cache CDN)的缓存的有效时间的,只对 public 缓存有效,cache-control: max-age=3600, s-maxage=31536000
         iii. no-cache:为资源设置了 no-cache 后,每一次发起请求都不会再去询问浏览器的缓存情况,而是直接向服务端去确认该资源是否过期,直接进行协商缓存
         iv. no-store:不使用任何缓存策略,每次请求都直接从服务器获取,并在浏览器客户端不进行资源缓存。

  • > cache-control 和 expires 并存
         cache-control的优先级更高,当cache-control与 expires同时出现时,以cache-control为准,不过考虑向下兼容性可以选择同时使用两种缓存策略。

2)协商缓存

     协商缓存依赖于服务端与浏览器之间的通信,在第一次获取资源时浏览器会存储HTTP请求的response headers字段:Last-Modified / Etag,当强缓存未命中的时候,它的值作为浏览器和服务器通信时携带的标志位用于判断资源是否过期,如果服务器判断资源过期的话就会重新下载资源,并更新相应标志位。如果判断资源未更新的话,会返回304状态码,浏览器就会复用客户端缓存资源。

  • > Last-Modified 和 If-Modified-Since 方式
         Last-Modified为随服务器端HTTP响应头部返回的时间戳标志,表示一个资源最近一次被更新的时间,客户端请求资源时添加上request headers字段If-Modified-Since(值与Last-Modified相同)用于服务器做校验判断资源是否更新,Last-Modified: Wed, 13 Jan 2021 15:34:55 GMT
    使用 Last-Modified 存在一些弊端:
         i. 命中失误1: 当我们更新了服务器的某个资源文件,但其实际内容并未发生变化,其相应的资源更新时间戳会改变,浏览器端在服务端文件并未发生改变的情况下,仅仅通过时间戳这种判断方式也会导致资源被完全重新下载。
         ii. 命中失误2: If-Modified-Since 只能检查到以秒为最小计量单位的时间差,感知不到1s以内的文件改动的情况,这会导致一些浏览器缓存更新不及时的情况。

  • > Etag 和 If-None-Match 方式
         Etag就是为了弥补Last-Modified的弊端而产生的新的协商缓存方式。Etag为随服务器端HTTP请求头部返回的资源唯一标志,例如:ETag: W/"2a3b-1602480f459",它根据资源内容而生成,可以精确感知资源的变动情况,即使多次更新,只要内容不变,Etag值也是不会变化的。浏览器下一次请求此资源时,request headers里就会带上一个值相同的名为if-None-Match的字段用于服务器对此资源做对比,If-None-Match: W/"2a3b-1602480f459"

  • > Etag在感知文件变化上比Last-Modified更加准确,优先级也更高,不过Etag的生成会消耗掉部分服务器的性能,它可以作为一种辅助协商缓存方式与前者相互配合使用。当EtagLast-Modified同时存在时,以Etag为准。

Push Cache(优先级最低)


Push Cache 是指 HTTP2 在 server push 阶段存在的缓存:

  • Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。
  • Push Cache 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。
  • 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。
[ loading ]⇷⇷