面试总结之那些缓存的事

105 阅读9分钟

缓存是网络世界中非常重要的一环,也是解决性能问题最常用的手段之一,判断一个网站的性能,最直观的就是看网页的打开速度。

缓存的分类

HTTP 缓存(或 Web 缓存)是用于临时存储(缓存)Web文档(如 HTML 页面和图像),以减少服务器延迟的一种信息技术。HTTP 缓存系统会保存下通过这套系统的文档的副本;如果满足某些条件,则可以由缓存满足后续请求。HTTP 缓存系统既可以指设备,也可以指计算机程序。

《HTTP 权威指南》中介绍到缓存:

在前端开发中,性能一直都是被大家所重视的一点,然而判断一个网站的性能最直观的就是看网页打开的速度。其中提高网页反应速度的一个方式就是使用缓存。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷

优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,缓存文件可以重复使用,减少带宽,降低网络负荷

  • 页面加载和呈现速度更快
  • 减少不必要的数据传输,节省网络流量和带宽
  • 减少服务器负担
按缓存位置分类:
  • memory cache
  • disk cache
  • Service Worker 等

浏览器的资源缓存分为 from disk cache 和 from memory cache 两类。当首次访问网页时,资源文件被缓存在内存中,同时也会在本地磁盘中保留一份副本。当用户刷新页面,如果缓存的资源没有过期,那么直接从内存中读取并加载。当用户关闭页面后,当前页面缓存在内存中的资源被清空。当用户再一次访问页面时,如果资源文件的缓存没有过期,那么将从本地磁盘进行加载并再次缓存到内存之中

按失效策略分类
  • 强缓存
  • 协商缓存

强缓存:有效时间内不去请求服务器,而是直接使用缓存。实质是判断缓存资源是否超出某个时间或者某个时间段。强缓存是否使用的完全由浏览器作出判断。 这涉及到一个缓存有效时间的判断。在有效时间判断上,HTTP 1.0 和 HTTP 1.1 是有所不同的。比如:

HTTP 1.0 版本中规定响应头字段 Expires,它对应一个未来的时间戳。客户端第一次请求之后,服务端下发 Expires 响应头字段,当客户端再次需要请求时,先会对比当前时间和 Expires 头中设置的时间。如果当前时间早于 Expires 时间,那么直接使用缓存数据;反之,需要再次发送请求,更新数据

Expires存在的缺点

  • Expires是一个绝对时间,可能会因为服务器和客户端的GMT时间不同,出现偏差
  • 手动修改本地的设备时间,客户端的时间就可能出现不准确的现象
  • 写法方面,字符串多一个空格,少一个字母,都会导致非法属性从而设置失效

HTTP1.1Cache-control 这个响应头出现,解决Expires 的问题,其有以下值:

  • private:表示私有缓存,不能被共有缓存代理服务器缓存,不能在用户间共享,可被用户的浏览器缓存。
  • public:表示共有缓存,可被代理服务器缓存,比如 CDN,允许多用户间共享
  • max-age:值以秒为单位,表示缓存的内容会在该值后过期
  • no-cache:需要使用协商缓存,注意这个字段并不表示不使用缓存
  • no-store:所有内容都不会被缓存
  • must-revalidate:告诉浏览器,你这必须再次验证检查信息是否过期, 返回的代号就不是 200 而是 304 了

HTTP 规定,如果 Cache-control 的 max-age 和 Expires 同时出现,那么 max-age 的优先级更高,他会默认覆盖掉 expires

协商缓存

在很多情况下,强制缓存超出了这个时间段,但是资源并没有更新,所以从优化角度来说,其实我们更重点应该关心关心服务器文件是否发生了变化,所以就有协商缓存策略。因为浏览器不知道服务器的文件是否已经发生了变化,所以协商缓存需要将是否使用缓存的决定权交给服务端,即协商缓存还是需要一次网络请求的。 协商缓存的过程:

  • 在浏览器中,某个资源没有命中强缓存,浏览器就会发送一个请求到服务器,验证协商缓存是否命中,如果命中,返回HTTP状态为 304

  • 那服务器怎么判断资源有没有过期呢?这里是重点: 这个决断是根据【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对 header 来作出的

    • 在浏览器第一次请求资源,服务端会在返回资源的响应头中加入 Last-Modified 字段,这个字段表示这个资源在服务器上的最近修改时间
    • 浏览器收到响应,并记录 Last-Modified 这个响应头的值为 T
    • 当浏览器再次向服务端请求该资源时,请求头加上 If-Modified-Since 的 header,这个 If-Modified-Since 的值正是上一次请求该资源时,后端返回的 Last-Modified 响应头值 T
    • 服务端再次收到请求,根据请求头 If-Modified-Since 的值 T,判断相关资源是否在 T 时间后有变化;如果没有变化则返回 304 Not Modified,且并不返回资源内容,浏览器使用资源缓存值;如果有变化,则正常返回资源内容,且更新 Last-Modified 响应头内容 我们思考这种基于时间的判断方式和 HTTP 1.0 的 Expires 的问题类似,如果客户端的时间不准确,就会导致判断不可靠;同时 Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间;也要考虑到,一些文件也许会周期性的更改,但是他的内容并不改变,仅仅改变的修改时间,这时候使用 Last-Modified 就不是很合适了。为了弥补这种小缺陷,就有了 【ETag、If-None-Match】这一对 header 头来进行协商缓存的判断 ** **【ETag、If-None-Match】这一对 header 主导的协商缓存过程:
  • 浏览器第一次请求资源,服务端在返回资源的响应头中加入 Etag,Etag 能够弥补 Last-Modified 的问题,因为 Etag 的生成过程类似文件 hash 值,Etag 是一个字符串,不同文件内容对应不同的 Etag 值

  • 浏览器收到响应,记录 Etag 这个响应头的值为 E

  • 浏览器再次跟服务器请求这个资源时,在请求头上加上 If-None-Match,值为 Etag 这个响应头的值 E

  • 服务端再次收到请求,根据请求头 If-None-Match 的值 E,根据资源生成一个新的 ETag,对比 E 和新的 Etag:如果两值相同,则说明资源没有变化,返回 304 Not Modified,同时携带着新的 ETag 响应头;如果两值不同,就正常返回资源内容,这时也更新 ETag 响应头

  • 浏览器收到 304 的响应后,就会从缓存中加载资源

这里需要重点说明一下的是 Etag 的生成策略,实际上规范并没有强制说明,这就取决于各大厂商或平台的自主实现方式了:Apache 中,ETag 的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行混淆后得到的;MDN 使用 wiki 内容的十六进制数字的哈希值。

另外一个需要注意的细节是:Etag 优先级比 Last-Modified 高,如果他们组合出现在请求头当中,我们会优先采用 Etag 策略。同时 Etag 也有自己的问题:相同的资源,在两台服务器产生的 Etag 是不是相同的,所以对于使用服务器集群来处理请求的网站来说, Etag 的匹配概率会大幅降低。所以在这种情况下,使用 Etag 来处理缓存,反而会有更大的开销

关于 Cache-control 取值总结可总结为如下图:

缓存和浏览器的操作

常见浏览器行为对应的缓存有哪些呢?这里做一下总结:(注意:不同的浏览器引擎,不同版本可能会有差别)

  • 使用Ctrl + F5强制刷新网页时, 直接从服务器加载,跳过强制缓存和协商缓存
  • 当用户仅仅是敲击了F5刷新网页时,跳过强制缓存,但是仍然会进行协商缓存

实战FAQ

如何禁止浏览器不缓存静态资源?

  1. chrome无痕模式
  2. 代码层面设置:
  • Cache-Control: no-cache, no-store, must-revalidate

  • 或者给请求的资源增加一个版本号<link rel="stylesheet" type="text/css" href="./asset.css?version=1.8.

  • 也可以使用 Meta 标签来声明缓存规则:<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>

Q: 设置以下 request/response header 会有什么效果?

Cache-Control: max-age=60, must-revalidate

A:如果资源在 60s 内再次访问,走强缓存,可以直接返回缓存资源内容;如果超过 60s,则必须发送网络请求到服务端,去验证资源的有效性

为什么大厂都不怎么用 Etag

大厂多使用负载分担的方式,来调度HTTP请求。因此同一个客户端多同一个页面的多次请求,很可能会分配到不同的服务器来响应,而根据ETag的计算原理,不同的服务器,有可能在资源没有变化的情况下,计算出不一样的ETag,而使缓存失效。

Yahoo 的 YSlow 页面分析工具为什么推荐关闭 ETag?

A:因为 Etag 计算较为复杂,所以可能会使得服务端响应变慢

www.alloyteam.com/2012/03/web…