Skip to content

一文搞懂HTTP浏览器缓存策略

1.浏览器缓存介绍

浏览器缓存是一种将资源(如网页、图像、样式表、脚本等)保存在用户计算机本地存储介质上的技术,以便在未来请求同一资源时能够更快地获取到。浏览器缓存有助于提高网站性能、降低服务器负载以及减少页面加载时间。

浏览器缓存通常基于HTTP协议的缓存机制来实现,以下是浏览器缓存的主要实现方式:

  1. HTTP头部的缓存控制:通过在HTTP响应头部设置缓存相关的HTTP头信息,服务器可以告诉浏览器如何缓存资源。常见的HTTP头部包括:
    • Cache-Control:用于定义缓存策略,如max-age指定资源可以被缓存的最大时间。
    • Expires:指定资源的过期时间,过了这个时间后,浏览器将重新请求资源。
    • ETagLast-Modified:用于标识资源版本,浏览器可以根据这些信息判断资源是否需要重新获取。
  2. 浏览器缓存存储介质:浏览器通常使用以下两种主要的缓存存储介质:
    • 内存缓存浏览器将一部分缓存保存在内存中,这些缓存通常有较短的生命周期,用于存储临时数据,例如当前页面的样式表和脚本
    • 磁盘缓存浏览器将另一部分缓存保存在磁盘上,这些缓存通常有较长的生命周期,用于存储较大的资源,例如图片、字体、JavaScript库等。
  3. 缓存策略:浏览器根据服务器返回的缓存策略以及之前的缓存信息来决定是否从缓存中获取资源或从服务器重新请求资源。常见的缓存策略包括:
    • 强制缓存:通过Cache-ControlExpires头部来指定资源的有效期,如果在有效期内,浏览器将直接从缓存中获取资源。
    • 协商缓存:通过ETagLast-Modified头部来标识资源的版本,如果资源未过期但需要验证,浏览器会发送请求到服务器,服务器会根据这些信息判断是否返回新的资源。
  4. 用户操作:用户可以通过浏览器设置来清除缓存,或者使用浏览器的**"强制刷新"功能来忽略缓存,重新请求资源**。

总结来说,浏览器缓存是通过HTTP头部控制和存储在内存和磁盘中的方式来实现的。通过合理设置缓存策略,网站开发人员可以控制哪些资源应该缓存,以提高性能并减少服务器负载。同时,用户也可以通过清除缓存来解决一些问题,或者强制刷新以获取最新的资源。浏览器缓存是Web性能优化中的重要一环。

注意:如果面试题是说介绍一下浏览器缓存,浏览器缓存是怎么做的,这个时候不要直接说到缓存策略,而是应该像上面这么说。

2.浏览器缓存策略:强缓存和协商缓存

缓存策略: 缓存策略指定了浏览器在何时以及如何使用缓存来加载资源。常见的缓存策略包括:

1.强缓存

强缓存:浏览器在缓存有效期内(比如 Cache-Control 或 Expires 设置的时间范围内)直接使用缓存,不发送请求到服务器。

浏览器不会向服务器发送任何请求,直接从本地缓存中读取文件并返回,关闭浏览器后,依然存在 ——> 每隔一段时间进行请求(相当于设置cookie过期时间的那种缓存)

主要的实现方式:Cache-Control字段和Expires字段

(1)Cache-Control请求头和响应头

Cache-Control 字段是用于控制缓存策略的 HTTP 响应头部,它可以实现强缓存,即在一定时间内,客户端可以直接使用本地缓存而无需向服务器发送请求。以下是实现强缓存的过程:

  1. 服务器设置 Cache-Control 头部:当服务器响应客户端的请求时,服务器会在 HTTP 响应头部中设置 Cache-Control 字段,并指定相关的缓存指令。常见的缓存指令包括:
    • max-age=<seconds>:指定资源的最大缓存时间,以秒为单位。例如,Cache-Control: max-age=3600 表示资源可以在客户端缓存中存储一个小时。
    • public:表示响应可以被任何缓存存储,包括共享代理服务器和浏览器缓存。
    • private:表示响应只能被单个用户的私有缓存存储,不允许共享缓存。
    • no-store:要求不对响应进行缓存,即不允许缓存。
    • no-cache:要求缓存在使用响应之前必须将其验证,通常与协商缓存一起使用。
  2. 客户端接收响应:客户端接收到包含 Cache-Control 头部的响应后,会将响应以及相关的缓存指令保存在本地缓存中。
  3. 后续请求时的缓存检查当客户端需要再次请求相同的资源时,在发送请求之前,它会首先检查本地缓存中的资源是否仍然在有效期限内,即是否未过期。
  4. 缓存有效,直接使用:如果资源仍然在有效期限内,客户端可以直接使用本地缓存,无需向服务器发送请求。这样可以减少网络流量和提高页面加载速度。
  5. 缓存过期,向服务器发起请求:如果资源已经过期,客户端会向服务器发送请求,服务器会返回新的资源或者告诉客户端资源未发生变化(使用协商缓存)。

在这里插入图片描述

(2) Expires

其实整个过程可以说和Cache-Control是一摸一样的!也是标志的过期时间

Expires 头部的缺点是它依赖于服务器和客户端的时钟同步,因为过期时间是在服务器端生成并发送给客户端的。如果服务器和客户端的时钟不同步,可能会导致缓存过期时间的计算出现问题。因此,现代的 Web 开发中更常使用基于响应头部的 Cache-Control 头部来指定缓存策略,因为它更加灵活且不依赖于时钟同步。

为什么Cache-Control不依赖于时钟同步?

  1. 相对时间指令Cache-Control 中的缓存指令(如 max-age)是基于相对时间的。例如,max-age=3600 表示资源可以在缓存中存储一个小时,而不是依赖于绝对时间。这意味着客户端只需关心资源在当前请求时是否在有效期限内,而不需要担心服务器和客户端的时钟同步问题。

  2. 当前请求时间:客户端在每个请求中都会获取当前的时间戳,因此它可以根据当前请求的时间戳,与过期时间的计算结果进行比较,来判断资源是否在有效期内。这个时间戳是客户端自己生成的,与服务器的时钟无关。

    过期时间是怎么计算到的?(在接收响应的时候计算的)

    Cache-Control 中的 max-age 指令是相对时间的一种,它用于指定从响应接收后的一段时间内,缓存可以继续使用响应的内容而不需要再次验证。要确定缓存是否过期,缓存系统会使用以下步骤来比较 max-age

    1. 当接收响应时,缓存系统接收到一个响应(例如,来自服务器的 HTTP 响应),它会记录下接收响应的时间点,即 "接收时间"。
    2. 缓存系统会检查响应的 Cache-Control 头中是否包含 max-age 指令。如果存在,它会获取 max-age 指令的值,该值表示响应的最大缓存时间(以秒为单位)。
    3. 缓存系统会获取当前时间,即 "当前时间"。
    4. 缓存系统会计算出 "接收时间" 加上 max-age 指令的值,得到一个时间点,即 "过期时间"
    5. 当发送请求时,缓存系统会将 "当前时间" 与 "过期时间" 进行比较
      • 如果 "当前时间" 小于 "过期时间",则缓存仍然有效,可以继续使用,因为它还没有过期。
      • 如果 "当前时间" 大于或等于 "过期时间",则缓存被认为已过期,需要重新验证或从服务器获取新的资源。

2.协商缓存

在缓存过期或浏览器主动刷新时,浏览器会发送请求到服务器,服务器判断资源是否有变化(通过 ETag 或 Last-Modified 等字段),如果没有变化,服务器返回 304 Not Modified,浏览器会使用本地缓存,否则返回新的资源内容。

协商缓存是一种服务端缓存策略,即通过服务端来判断某件事情是不是可以被缓存,或者说每次请求都要检查这个缓存是否还有效。 ——> 两种实现方式:ETag,Last-Modified

1、实现方式一:Last-Modified + If-Modified-Since实现

当浏览器第一次发请求时,服务器返回资源并在返回头中返回一个Last-Modified 的值给浏览器。这个Last-Modified 的值给到浏览器之后,浏览器会通过 If-Modified-Since 的字段来保存 Last-Modified 的值,且 If-Modified-Since 保存在请求头当中。

当浏览器发送一个带有 If-Modified-Since 头部的HTTP请求到服务器时,服务器会根据这个头部的值执行以下工作:

  1. 服务器检查资源的最后修改时间(Last-Modified):服务器首先会检查请求中的 If-Modified-Since 头部的值,该值是上一次从服务器请求资源时,服务器响应中的 Last-Modified 头部的值。服务器会将这个值与当前资源的最后修改时间进行比较。

  2. 比较最后修改时间:服务器将请求中的 If-Modified-Since 的值与资源的实际最后修改时间进行比较。

    • 如果资源的最后修改时间早于 If-Modified-Since 的值,表示资源在客户端上的缓存是最新的,服务器会返回一个状态码 304(Not Modified),并且响应不包含实际资源内容

    • 如果资源的最后修改时间等于或晚于 If-Modified-Since 的值,表示资源已经被修改,服务器会正常返回新的资源内容,同时在响应中包含新的 Last-Modified 头部,以更新客户端的缓存信息。

      注意:并没有发生第二次请求,只是一次就直接获取要么304,要么200+最新的数据

    实际最后修改时间服务器是怎么知道的?

    1. 文件系统信息:如果服务器上的资源是静态文件(例如HTML、CSS、JavaScript、图像等),服务器可以使用文件系统的元数据来获取资源的最后修改时间。不同操作系统提供了不同的API来访问这些信息。
    2. 数据库记录:对于动态生成的内容,服务器通常会将资源的最后修改时间存储在数据库中的相关记录中。当请求到来时,服务器可以查询数据库来获取资源的最后修改时间。
    3. 自定义逻辑:有些服务器可能会通过自定义逻辑来确定资源的最后修改时间。这可以包括根据特定条件计算最后修改时间或者从外部数据源获取最后修改时间。
  3. 客户端处理响应:当浏览器收到服务器的响应时,会根据状态码来处理响应:

    • 如果收到状态码 304,浏览器会从本地缓存中获取资源,因为资源没有被修改,仍然有效。
    • 如果收到其他状态码(如200 OK),表示资源已经被修改,浏览器会更新本地缓存,并使用新的资源内容。

这个机制允许服务器在资源没有实际修改时,通过状态码304来告知浏览器可以使用缓存的资源,从而减少网络流量和提高页面加载速度。这种基于If-Modified-SinceLast-Modified的缓存策略是一种协商缓存的方式,用于优化Web资源的传输。

详细过程

在这里插入图片描述

2、实现方式二:ETag + If-None-Match实现

基本和last-modify一样,但是会更加优秀!

Etag(实体标签)是HTTP协议中用于实现协商缓存的一种机制。Etag的工作方式如下:

  1. 服务器生成Etag值

    • 当客户端首次请求资源时,服务器会生成一个唯一的Etag值,通常是资源内容的哈希值或者一些其他生成规则,以表示资源的版本。 ——> Etag标志值是资源内容而不仅仅是修改时间(可能没有修改但是时间变了,这个时候理论上缓存是没有过期的!)

      一般怎么生成?

      服务器通常会通过以下方式生成当前资源的 Etag 值:

      1. 文件内容的哈希值:对于静态文件(如HTML、CSS、JavaScript、图像等),服务器可以通过计算文件内容的哈希值来生成 Etag 值。常用的哈希算法包括MD5、SHA-1、SHA-256等。文件内容的任何更改都会导致哈希值的变化,从而产生新的 Etag 值。
      2. 资源元数据的组合:对于动态生成的内容,服务器可以将资源的元数据组合在一起,包括内容的版本、大小、最后修改时间等。将这些元数据拼接成一个字符串,并计算哈希值,生成 Etag 值。
      3. 特定规则生成:有些服务器可能根据资源的特定属性或规则来生成 Etag 值。例如,对于一个博客文章,Etag 值可以由文章的发布日期和标题组成的字符串生成。
      4. 外部数据源的信息:在某些情况下,服务器可能需要从外部数据源(如数据库)获取一些信息来生成 Etag 值。这通常涉及到将多个数据项组合在一起,并生成唯一的标识。

      Etag 作为一种用于缓存控制的机制,通常是基于资源内容生成的,以确保缓存的资源与实际资源一致。不过,服务器可以根据实际需求采用不同的方式生成 Etag 值,只要保证生成的值在资源内容发生变化时能够随之变化即可。

      不过,由于 Etag 生成需要计算哈希值或其他标识符,可能会消耗一些计算资源,因此在某些情况下,服务器可能更喜欢使用 Last-Modified 头部作为缓存控制机制,特别是对于静态文件而言。选择使用哪种机制取决于资源的特性以及服务器和客户端的需求。

      注意:由于Etag一般是基于内容进行生成hash值的,所以就没必要像Last-Modified一样把标志值缓存在数据库里面了,而是直接在需要比较的时候临时计算即可!消耗计算资源但是减少存储资源!

    • 服务器将这个Etag值作为HTTP响应头部的一部分返回给客户端,通常在响应头部中的 ETag 字段。

    http
    HTTP/1.1 200 OK
    Date: Thu, 01 Sep 2023 00:00:00 GMT
    ETag: "abc123"  // 服务器生成的Etag值
    Content-Length: 1234
    Content-Type: text/html
  2. 客户端缓存资源和Etag值

    • 客户端接收到资源后,会将该资源及其对应的Etag值保存在本地缓存中。
  3. 后续请求时的协商缓存

    • 当客户端需要再次请求相同的资源时,它会在请求头部添加一个名为 If-None-Match 的字段,该字段的值是上次获取到的Etag值。
    http
    GET /resource HTTP/1.1
    Host: example.com
    If-None-Match: "abc123"  // 上次获取的Etag值
  4. 服务器判断Etag值

    • 服务器收到客户端的请求后,会比较请求中的 If-None-Match 值与当前资源的Etag值是否匹配。
    • 如果匹配,表示资源未发生变化,服务器会返回状态码304(Not Modified)作为响应,告诉客户端可以继续使用本地缓存。
    http
    HTTP/1.1 304 Not Modified
    Date: Thu, 01 Sep 2023 00:10:00 GMT
    ETag: "abc123"  // 当前资源的Etag值
    • 如果不匹配,表示资源已经发生了变化,服务器会正常返回新的资源内容,并更新 ETag 值。
    http
    HTTP/1.1 200 OK
    Date: Thu, 01 Sep 2023 00:20:00 GMT
    ETag: "xyz789"  // 新的Etag值
    Content-Length: 1234
    Content-Type: text/html

这样,通过Etag协商缓存,客户端和服务器可以根据Etag值来判断资源是否发生变化,从而决定是否重新获取资源或者使用本地缓存。这种机制相比于基于时间的协商缓存(如Last-Modified头部)更精确,因为它考虑了资源内容的变化而不仅仅是修改时间。

3.Etag和last-modify哪个优先级更高,为什么更推荐用Etag?

在 HTTP 协议中,ETag 和 Last-Modified 都是用于实现缓存机制的头部字段,但它们的优先级和使用场景有所不同。

  1. ETag 优先级更高: ETag 的优先级通常会高于 Last-Modified。**当客户端发送一个请求时,如果服务器返回了 ETag 和 Last-Modified 字段,浏览器会优先使用 ETag 进行比较来判断资源是否需要重新请求。**只有在服务器未提供 ETag 时,浏览器才会使用 Last-Modified。

  2. 为什么更推荐使用 ETag: ETag 比 Last-Modified 更推荐的原因主要有以下几点:

    • 时间精确性: ETag 是由服务器生成的唯一标识符(主要是基于内容,或者字段的组合),可以更精确地区分不同的资源版本,即使资源内容没有发生实际更改,只要 ETag 发生变化,浏览器也可以判定需要重新获取资源。而 Last-Modified 只能精确到秒级别,无法捕捉到毫秒级别的变化(所以基于时间并不是很靠谱的方案)。

    • 不受修改时间影响: 在一些情况下,即使资源内容没有实际变化,文件的修改时间可能会因为某些操作(如拷贝、备份)而更新,导致 Last-Modified 产生误判(所以基于时间并不是很靠谱的方案)。ETag 则不受这种影响(主要是基于内容,或者字段的组合)。

    • 非文件资源等类型: ETag 适用于任何类型的资源,包括动态生成的内容(主要是基于内容,或者字段的组合),而 Last-Modified 更适用于静态文件。

      简单来说就一句话:Etag主要是基于文件内容进行生成的,所以避免了很多和时间标志有关的问题,如时间精确度、时间变内容不变等问题!

总的来说,尽管 ETag 和 Last-Modified 都可以用于缓存机制,但 ETag 在精确性、可靠性以及适用范围上更具优势,因此更推荐使用 ETag 来判断资源是否需要重新请求。