资讯专栏INFORMATION COLUMN

深入剖析浏览器缓存策略

Cympros / 861人阅读

摘要:浏览器缓存只存在于每个多带带的客户端,因此它是私有缓存。表示该资源既能被浏览器缓存,也能被任何中间人比如代理服务器等缓存。普通刷新会启用协商缓存,忽略强缓存。只有在地址栏或收藏夹输入网址通过链接引用资源等情况下,浏览器才会启用强缓存。

前言

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

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

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

浏览器缓存的分类

如何启用和禁止缓存

缓存存储的位置

如何设置缓存的过期时间

缓存过期之后会发生什么

如何为自己的应用制定合适的缓存策略

调试

如何判断网站是否启用了缓存

如何禁用浏览器缓存

缓存存储 启用缓存

Cache-Control

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

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

但是我发现,即使 Response Header 中没有设置 Cache-Control 和 Expires,浏览器仍然会缓存某些资源。这是为什么呢?

原来当 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。

200 from Memory Cache

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

200 from Disk Cache

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

总的来说就是:

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

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

缓存分类

浏览器缓存一般分为两类:强缓存(也称本地缓存)和协商缓存(也称弱缓存)。判定过程如下:

浏览器发送请求前,会先去缓存里面查看是否命中强缓存,如果命中,则直接从缓存中读取资源,不会发送请求到服务器。否则,进入下一步。

当强缓存没有命中时,浏览器一定会向服务器发起请求。服务器会根据 Request Header 中的一些字段来判断是否命中协商缓存。如果命中,服务器会返回响应,但是不会携带任何响应实体,只是告诉浏览器可以直接从缓存中获取这个资源。否则,进入下一步。

如果前两步都没有命中,则直接从服务器加载资源。

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

缓存过期策略

当缓存过期之后,浏览器会向服务器发起 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 请求,以验证缓存的资源是否发生变化。

缓存验证时机

什么时候会进行缓存验证?

刷新页面。一般来说,为了确保用户获取到最新的数据,在刷新页面时大部分浏览器都不会再使用缓存中的数据,而是发起一个请求去服务器验证。

Response Header 中设置了 Cache-control: must-revalidate。当缓存的资源过期之后,必须到源服务器去验证,只有确认该资源没有过期,才能继续使用缓存。

缓存验证器

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

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,则说明该请求使用的是已被缓存的文件。如下:

调试时,如果想禁用浏览器缓存,可以在开发者工具上勾选 Disabel cache。

最后

最后,大家可以通过下面这张图再回顾一下我们刚刚讲过的内容。

参考

HTTP 缓存

奇怪的缓存策略

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/62091.html

相关文章

  • 从0到1玩转线程池

    摘要:提交任务当创建了一个线程池之后我们就可以将任务提交到线程池中执行了。提交任务到线程池中相当简单,我们只要把原来传入类构造器的对象传入线程池的方法或者方法就可以了。 我们一般不会选择直接使用线程类Thread进行多线程编程,而是使用更方便的线程池来进行任务的调度和管理。线程池就像共享单车,我们只要在我们有需要的时候去获取就可以了。甚至可以说线程池更棒,我们只需要把任务提交给它,它就会在合...

    darkerXi 评论0 收藏0
  • 性能优化全新思路!实践腾讯、字节、阿里、百度、网易等互联网公司项目实战+案例分析(附PDF源码)

    摘要:不努力不奋斗,可能就会在基层一辈子止步不前。不过,只一句,如果你还在做这一行,还是一名程序猿媛,想走上坡路的你,也许我这到手的十几家一线互联网公司性能优化项目实战可能会对你有所帮助。 ...

    ytwman 评论0 收藏0
  • 2017文章总结

    摘要:欢迎来我的个人站点性能优化其他优化浏览器关键渲染路径开启性能优化之旅高性能滚动及页面渲染优化理论写法对压缩率的影响唯快不破应用的个优化步骤进阶鹅厂大神用直出实现网页瞬开缓存网页性能管理详解写给后端程序员的缓存原理介绍年底补课缓存机制优化动 欢迎来我的个人站点 性能优化 其他 优化浏览器关键渲染路径 - 开启性能优化之旅 高性能滚动 scroll 及页面渲染优化 理论 | HTML写法...

    dailybird 评论0 收藏0
  • 2017文章总结

    摘要:欢迎来我的个人站点性能优化其他优化浏览器关键渲染路径开启性能优化之旅高性能滚动及页面渲染优化理论写法对压缩率的影响唯快不破应用的个优化步骤进阶鹅厂大神用直出实现网页瞬开缓存网页性能管理详解写给后端程序员的缓存原理介绍年底补课缓存机制优化动 欢迎来我的个人站点 性能优化 其他 优化浏览器关键渲染路径 - 开启性能优化之旅 高性能滚动 scroll 及页面渲染优化 理论 | HTML写法...

    hellowoody 评论0 收藏0

发表评论

0条评论

Cympros

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<