浏览器缓存浅析

浏览器缓存也就是 HTTP 缓存。缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当 Web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器端压力,提升性能(获

目录
  1. 什么是浏览器缓存
  2. 浏览器缓存位置
  3. 浏览器缓存机制
  4. 浏览器强缓存和协商缓存
  5. 实际场景应用缓存策略
  6. 用户行为对浏览器缓存的影响
  7. 如何实现缓存
  8. 浏览器缓存的重要性

什么是浏览器缓存#

浏览器缓存也就是 HTTP 缓存。缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当 Web 缓存发现请求的资源已经被存储,它会拦截请求,返回该资源的拷贝,而不会去源服务器重新下载。这样带来的好处有:缓解服务器端压力,提升性能(获取资源的耗时更短了)。对于网站来说,缓存是达到高性能的重要组成部分。缓存需要合理配置,因为并不是所有资源都是永久不变的:重要的是对一个资源的缓存应截止到其下一次发生改变(即不能缓存过期的资源)。(摘自 MDN )

浏览器缓存位置#

浏览器缓存的存储主要有“Service Worker”、“Memory Cache”、“Disk Cache” 和“Push Cache”。

  • Service Worker:运行在浏览器背后的独立线程。使用 Service Worker 的话,传输协议必须为 HTTPS。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的

    • 注册 Service Worker
    • 监听到 install 事件
    • 缓存需要的文件
  • Memory Cache:内存中的缓存。主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了内存缓存在缓存资源时并不关心返回资源的 HTTP 缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对 URL 做匹配,还可能会对Content-TypeCORS等其他特征做校验

  • Disk Cache:存储在硬盘中的缓存。读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。绝大部分的缓存都来自 Disk Cache。

  • Push Cache:Push Cache(推送缓存)是 HTTP2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在 Chrome 浏览器中只有 5 分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。

  • 所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差。

  • 可以推送 no-cache 和 no-store 的资源。

  • 一旦连接被关闭,Push Cache 就被释放。

  • 多个页面可以使用同一个 HTTP/2 的连接,也就可以使用同一个 Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的 tab 标签使用同一个 HTTP 连接。

  • Push Cache 中的缓存只能被使用一次。

  • 浏览器可以拒绝接受已经存在的资源推送。

  • 你可以给其他域名推送资源。

Memory Cache 和 Disk Cache 的存储区分比较复杂,但是有两点比较好区别:

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

浏览器缓存机制#

浏览器与服务器通信的方式为应答模式,即是:浏览器发起 HTTP 请求 – 服务器响应该请求,那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。具体过程如下图:

image.webp

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识。
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。

浏览器强缓存和协商缓存#

根据是否需要向服务器重新发起 HTTP 请求将缓存过程分为两个部分,分别是强缓存和协商缓存。

强缓存和协商缓存机制可以同时存在,强制缓存的优先级高于协商缓存,当执行强制缓存时,如若缓存命中,则直接使用缓存数据库数据,不在进行缓存协商。

强缓存 (本地缓存):不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 Network 选项中可以看到该请求返回 200 的状态码,并且 Size 显示 from disk cache 或 from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

  • Expires:http1.0 规范,在不支持 http1.1 时发挥作用。它的值是一个绝对时间的 GMT 格式的时间字符串,是这个资源的失效时间。Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。需要和 Last-modified 结合使用。
  • Cache-Control:http1.1 规范,优先级比 expires 更高,同时使用时选它。可以同时设置下面多个属性:
指令作用
max-age缓存有效时间
public表示相应被客户端和代理服务器缓存
private只能被客户端缓存
no-cache资源被缓存,但是立即失效,下次会发起请求验证资源是否有效
no-store不缓存任何相应
  • max-age=3600 有效时间为 3600 秒。

  • public 可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。

  • private 只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。

  • no-cache 不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在 ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。

  • no-store 直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。

image.webp

协商缓存 就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

  • 协商缓存生效,返回 304 和 Not Modified。

image.webp

  • 协商缓存失效,返回 200 和请求结果。

image.webp

  • Etag/If-None-Match: Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag 就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的 Etag 值放到 request header 里的 If-None-Match 里。服务器只需要比较客户端传来的 If-None-Match 跟自己服务器上该资源的 Etag 是否一致,就能很好地判断资源相对客户端而言是否被修改过了。

  • Last-Modified/If-Modified-Since: 浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-Modified,它是一个时间标识该资源的最后修改时间。当浏览器再次请求该资源时,request 的请求头中会包含 If-Modified-Since,该值为缓存之前返回的 Last-Modified。服务器收到 If-Modified-Since 后,根据资源的最后修改时间判断是否命中缓存。

    • 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
    • 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源

Etag 和 Last-Modified 对比

  • 首先在精确度上,Etag 要优于 Last-Modified。Last-Modified 的时间单位是秒,如果某个文件在 1 秒内改变了多次,那么他们的 Last-Modified 其实并没有体现出来修改,但是 Etag 每次都会改变确保了精度。如果是负载均衡的服务器,各个服务器生成的 Last-Modified 也有可能不一致。

  • 在性能上,Etag 要逊于 Last-Modified,毕竟 Last-Modified 只需要记录时间,而 Etag 需要服务器通过算法来计算出一个 hash 值,会消耗服务器性能。

  • 在优先级上,服务器校验优先考虑 Etag。

实际场景应用缓存策略#

  • 频繁变动的资源。对于频繁变动的资源,首先需要使用Cache-Control: no-cache使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。

  • 不常变化的资源。通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的max-age=31536000(一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。

用户行为对浏览器缓存的影响#

所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:

  • 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用。如没有则发送网络请求。
  • 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有Cache-control: no-cache(为了兼容,还带了Pragma: no-cache),服务器直接返回 200 和最新内容

如何实现缓存#

通过设置 HTTP 报文的响应头来设置缓存策略。

比如在使用 Node.js 框架 Koa2 时,设置静态资源的缓存时间。

const serve = require("koa-static");
 
app.use(
  serve(path.join(__dirname, "./static"), {
    maxAge: 1000 * 60 * 60 * 24 * 7,
  })
);
 
// ...

此时,浏览器会根据你设置的相关策略存储缓存。

浏览器缓存的重要性#

浏览器缓存在提高网页性能的时候非常重要,建议使用。