深刻剖析浏览器缓存策略

前言

在访问一个网页时,客户端会从服务器下载所需的资源。可是有些资源不多发生变更,例如 HTML、JS、CSS、图片、字体文件等。若是每次加载页面都从源服务器下载这些资源,不只会增长获取资源的时间,也会给服务器带来必定压力。所以,重用已获取的资源十分重要。将请求的资源缓存下来,下次请求同一资源时,直接使用存储的副本,而不会再去源服务器下载。这就是咱们常说的缓存技术。git

缓存的种类不少:浏览器缓存、网关缓存、CDN 缓存、代理服务器缓存等。这些缓存大体能够归为两类:共享缓存和私有缓存。共享缓存可以被多个用户使用,而私有缓存只能用于单个用户。浏览器缓存只存在于每一个单独的客户端,所以它是私有缓存。github

本文主要介绍私有(浏览器)缓存。你将学习:web

  • 浏览器缓存的分类
  • 如何启用和禁止缓存
  • 缓存存储的位置
  • 如何设置缓存的过时时间
  • 缓存过时以后会发生什么
  • 如何为本身的应用制定合适的缓存策略
  • 调试算法

    • 如何判断网站是否启用了缓存
    • 如何禁用浏览器缓存

缓存存储

启用缓存

Cache-Control

浏览器会根据 HTTP Response Headers 中的一些字段来决定是否要缓存该资源。经过设置 Response Headers 中的 Cache-Control 和 Expires 能够启用缓存,这样资源就会被缓存到客户端。typescript

Cache-Control 能够设置 private 、publicmax-age 、 no-cache 来启用缓存。浏览器

Cache-Control: private/public
Cache-Control: max-age=300
Cache-Control: no-cache
  • private :表示该资源只能被浏览器缓存。
  • public :表示该资源既能被浏览器缓存,也能被任何中间人(好比代理服务器、CDN 等)缓存。
  • max-age :表示该资源可以被缓存的最大时间。若是设置 max-age=0 ,该资源仍然会被浏览器缓存,只不过马上就过时了。
  • no-cache :该资源会被缓存,可是马上就过时了,所以须要先和服务器确认资源是否发生变化,只有当资源没有变化时,该缓存才会被使用,不然须要从服务器下载。至关于 max-age=0 。

Expires

Expires 标识了缓存的具体过时时间,来控制资源什么时候过时。经过设置 Expires 能够启用缓存。不过须要注意 Expires 的值是格林威治时间(Greenwich Mean Time, GMT),不是本地时间。缓存

Expires: Fri, 08 Mar 2029 08:05:59 GMT
Expires: 0 // Expires: 0 仍然会启用缓存,只不过缓存马上过时。

优先级

既然 Cache-Control 和 Expires 都可以启用缓存,那么问题来了,若是同时设置 Cache-Control: max-age=600 和 Expires: 0 ,那么浏览器应该如何缓存该资源呢?答案是只有 Cache-Control: max-age=600 生效。由于 Cache-Control 的优先级高于 Expires,若是同时设置了 Cache-Control 和 Expires,以 Cache-Control 为准。服务器

浏览器的默认行为

设置 Cache-Control 以后,能够看到浏览器确实启用了缓存(from disk cache)。以下所示:网络

Cache-Control:max-age=604800, must-revalidate, public

Screen Shot 2019-04-01 at 5.26.58 PM.png

可是我发现,即便 Response Header 中没有设置 Cache-Control 和 Expires,浏览器仍然会缓存某些资源。这是为何呢?工具

image.png

原来当 Response Header 中有 Last-Modified 可是没有 Cache-Control 和 Expires 时,浏览器会用一套本身的算法来决定这个资源会被缓存多长时间。这是浏览器为了提高性能进行的优化,每一个浏览器的行为可能不一致,有些浏览器上甚至没有这样的优化。所以,若是要启用缓存,仍是应该本身设置合适的 Cache-Control 和 Expires,不要依赖浏览器自身的缓存算法。固然,若是在调试时发现本应该更新的文件没有更新,也别忘了看看是否被浏览器缓存了。

禁止缓存

给 Cache-Control 设置 no-store 会禁止浏览器和中间人缓存该资源。在处理包含我的隐私数据或银行业务数据的资源时颇有用。

Cache-Control: no-store

缓存目标对象

通常来讲,浏览器缓存只能存储 GET 响应,例如 HTML、JS、CSS、图片等静态资源。由于这些资源不常常发生变化,因此缓存能够帮助提高获取资源的速度。可是像一些 POST/DELETE 请求,这些请求基本上每一次都不同,所以也没有什么缓存的价值。

缓存位置

浏览器能够在内存、硬盘中开辟一个空间用以保存请求资源的副本。咱们常常在 Dev Tools 里面看到 Memory Cache(内存缓存)和 Disk Cache(硬盘缓存),指的就是缓存所在的位置。请求一个资源时,会按照优先级(Service Worker -> Memory Cache -> Disk Cache -> Push Cache)依次查找缓存,若是命中则使用缓存,不然发起网络请求。这里只介绍经常使用的 Memory Cache 和 Disk Cache。

Screen Shot 2019-04-02 at 2.24.56 PM.png

200 from Memory Cache

表示不访问服务器,直接从内存中读取缓存。由于缓存的资源保存在内存中,因此读取速度较快,可是关闭进程以后,缓存的资源也会随之销毁。通常来讲,系统不会给内存分配较大的容量,所以内存缓存通常用于存储小文件。同时,内存缓存在有时效性要求的场景下也颇有用(好比浏览器的隐私模式)。

200 from Disk Cache

表示不访问服务器,直接从硬盘中读取缓存。与内存相比,硬盘的读取速度较慢,可是硬盘缓存持续的时间更长,关闭进程以后,缓存的资源仍然存在。因为硬盘的容量较大,所以通常用于存储大文件。

总的来讲就是:

内存缓存:读取快、持续时间短、容量小

硬盘缓存:读取慢、持续时间长、容量大

缓存分类

浏览器缓存通常分为两类:强缓存(也称本地缓存)和协商缓存(也称弱缓存)。断定过程以下:

  1. 浏览器发送请求前,会先去缓存里面查看是否命中强缓存,若是命中,则直接从缓存中读取资源,不会发送请求到服务器。不然,进入下一步。
  2. 当强缓存没有命中时,浏览器必定会向服务器发起请求。服务器会根据 Request Header 中的一些字段来判断是否命中协商缓存。若是命中,服务器会返回响应,可是不会携带任何响应实体,只是告诉浏览器能够直接从缓存中获取这个资源。不然,进入下一步。
  3. 若是前两步都没有命中,则直接从服务器加载资源。

强缓存和协商缓存的共同点在于,若是命中,都是从客户端缓存中加载资源,而不是从服务器加载资源。而不一样点在于,强缓存不发送请求到服务器,而协商缓存会发送请求到服务器以验证资源是否过时。普通刷新会启用协商缓存,忽略强缓存。只有在地址栏或收藏夹输入网址、经过连接引用资源等状况下,浏览器才会启用强缓存。

缓存过时策略

当缓存过时以后,浏览器会向服务器发起 HTTP 请求,以肯定资源是否发生了变化。若是资源未改变,那么浏览器会继续使用本地的缓存资源;若是该资源已经发生变化了,那么浏览器会删除旧的缓存资源,并将新的资源缓存到本地。

过时时间

Http Response Header 里面的 Cache-Control: max-age=xxx 和 Expires 均可以设置缓存的过时时间,可是它们有一些区别:

Expires :标识该资源过时的时间点,它是一个绝对值,即在这个时间点以后,缓存的资源过时。
max-age :标识该资源可以被缓存的最大的时间。它是一个相对值,相对于第一次请求该文档时服务器记录的「请求发起时间」。

虽然 Cache-Control 是 HTTP 1.1 提出来的新特性,但并非说 max-age 优于 Expires。它们都有各自的使用场景,咱们应该根据业务需求去决定使用哪个。好比当某个资源须要在特定的时间点过时时应该使用 Expires 。若是只是为了开启缓存,使用 max-age 可能会更好些,由于 Cache-Control 的优先级高于 Expires。

针对应用中几乎不会改变的文件,一般能够设置一个较长的过时时间,以保证缓存的有效。例如图片、CSS、JS 等静态资源。

缓存验证

上一小节已经提到,当浏览器请求一个资源时,若是发现缓存中有该资源,可是已通过期了,那么浏览器就会向服务器发起 HTTP 请求,以验证缓存的资源是否发生变化。

缓存验证时机

何时会进行缓存验证?

  1. 刷新页面。通常来讲,为了确保用户获取到最新的数据,在刷新页面时大部分浏览器都不会再使用缓存中的数据,而是发起一个请求去服务器验证。
  2. Response Header 中设置了 Cache-control: must-revalidate。当缓存的资源过时以后,必须到源服务器去验证,只有确认该资源没有过时,才能继续使用缓存。

缓存验证器

服务器是怎么判断资源改变与否的呢?服务端在返回响应内容的同时,还会在 Response Header 中设置一些验证标识,当缓存的资源过时以后,浏览器就会携带验证标识向服务器发起请求,服务器经过对比这些标识,就能知道缓存的资源是否发生了改变。

image.png

Header 中的验证标识字段主要有两组:Etag 和 If-None-Match 、Last-Modified 和 If-Modified-Since 。其中,形如 If-xxx 这样的请求首部字段,能够称之为条件请求。好比只在知足某个条件的状况下返回或上传文件,这样能够节省带宽。

 

Last-Modified

Last-Modified 就是一个验证器。服务器在将资源返回给客户端的同时,会将资源的最后修改时间 Last-Modified 加在 Response Header 中一块儿返回。浏览器会为资源标记上该信息,当缓存过时以后,浏览器会把该信息设置到 Request Header 中的 If-Modified-Since 中向服务器发起请求。

若是 If-Modified-Since 中的值和服务器上该资源最终的修改时间一致,就说明该资源没有被修改过,服务器会直接返回 304 状态码,无响应实体,这样就能够节省传输的数据量。若是不一致,服务器会返回 200 状态码,同时和第一次 HTTP 请求同样,返回响应实体和验证器。

Last-Modified:Fri, 04 Jan 2019 14:00:21 GMT

Etag

服务器会经过某种算法,为资源计算出一个惟一标识符,在把响应返回给客户端的时候,会在 Response Header 中加上 Etag: 惟一标识符 一块儿返回给客户端。

Etag:"952d03d8561454120b550f0a5679a172c4822ce8"

客户端会将 Etag 保存下来,后续请求时会将 Etag 做为 Request Header 中 If-None-Match 的值发给服务器。经过比对客户端发过来的 Etag 和服务器上保存的 Etag 是否一致,就可以知道资源是否发生了变化。若是资源没有发生变化,返回 304,客户端继续使用缓存。若是资源已经修改,则返回 200。

制定缓存策略

缓存真的能够说让咱们又爱又恨。在开发时,咱们常常遇到这样的问题:明明已经修改了这个文件,为何没有生效?好吧,文件被缓存了。。。可是在上线时,咱们又但愿文件尽量地被浏览器缓存,来提升性能。所以为本身的应用制定合适的缓存策略很是重要。

为静态资源设置较长缓存时间。

有些资源很长时间都不会改变,好比一些三方库,图片,字体文件等。能够为它们设置一个很长的过时时间,例如设定「一年」。

经过给文件名的惟一标识来确保文件修改生效。

有些时候为了解决 bug,咱们可能会修改一些文件,好比应用的 CSS、JS 等。若是这些文件已经被缓存,那么除非用户强制刷新页面,不然用户只有在缓存过时以后才有可能获取新的文件。如何让浏览器不使用缓存,而是从新下载新的文件呢?有一个办法就是给文件名加上惟一标识,好比 Hash 或版本信息。当文件修改以后,这个惟一标识也会随之改变。浏览器发现文件改变以后,就不会使用缓存了。

明确是否有资源不能被缓存。

好比一些敏感数据,若是不该该被浏览器缓存,须要在 Response Header 中设置 Cache-Control: no-store。

调试

如何知道请求的资源是否被缓存了?打开 Chrome 的开发者工具,咱们能够看到 Size 这一栏下面,若是显示文件真实的大小,则说明该文件未被缓存。若是显示 from xxx cache,则说明该请求使用的是已被缓存的文件。以下:

Screen Shot 2019-04-03 at 11.16.58 AM.png

调试时,若是想禁用浏览器缓存,能够在开发者工具上勾选 Disabel cache。

Screen Shot 2019-04-03 at 11.36.57 AM.png

最后

最后,你们能够经过下面这张图再回顾一下咱们刚刚讲过的内容。

参考

相关文章
相关标签/搜索