Skip to content
Scroll to top↑

HTTP 缓存

HTTP 缓存是 Web 性能优化的关键一环。通过缓存,可以减少网络请求,加快页面加载速度,并降低服务器负载。HTTP 缓存主要通过一系列请求头和响应头来控制。

缓存控制 (Cache-Control)

Cache-Control 是 HTTP/1.1 中用于控制缓存行为的主要头部字段,它提供了丰富的指令来定义缓存策略。

  • max-age=<seconds>:指定资源能够被缓存的最长时间(以秒为单位)。例如 max-age=3600 表示资源在 1 小时内有效。这是控制缓存生命周期的首选方式。
  • no-store:禁止缓存任何版本的响应。每次请求都必须发送到源服务器,不应在任何缓存中存储。
  • no-cache:允许缓存,但在使用缓存副本之前,必须向源服务器进行验证(Revalidation),以确保资源没有过期。通常与 ETagLast-Modified 配合使用。如果资源未更改,服务器返回 304 Not Modified
  • must-revalidate:一旦资源过期(例如,超过 max-age),缓存必须在向客户端提供服务前验证其状态。这可以防止在网络断开等特殊情况下使用过期的资源。
  • public:表明响应可以被任何缓存(包括浏览器、CDN 等共享缓存)缓存。
  • private:响应只能被单个用户的私有缓存(通常是浏览器缓存)存储,不能被共享缓存(如 CDN)缓存。适用于包含用户个人信息的内容。
  • immutable:表示响应正文在未来不会改变。当资源被标记为 immutable 时,客户端在缓存有效期内不需要发送验证请求。这对于版本化的静态资源(如 style.v123.css)非常有用。

缓存验证

当缓存过期或 Cache-Control 指令要求验证时,客户端会使用条件请求头来询问服务器资源是否发生了变化。

Last-Modified & If-Modified-Since

  • Last-Modified (响应头): 服务器在响应中告知客户端资源的最后修改时间。
    http
    Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
  • If-Modified-Since (请求头): 当缓存过期时,客户端在请求中携带此头部,其值为之前收到的 Last-Modified 时间。服务器比较该时间与资源的实际修改时间:
    • 如果资源未修改,返回 304 Not Modified,不包含响应正文。
    • 如果资源已修改,返回 200 OK 和新的资源内容。

缺点: Last-Modified 的精度只能到秒,且在分布式系统中,不同服务器上的文件修改时间可能不一致。

ETag & If-None-Match

为了解决 Last-Modified 的问题,引入了 ETag (Entity Tag)。

  • ETag (响应头): 服务器为资源生成的唯一标识符,通常是文件内容的哈希值或版本号。
    http
    ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
  • If-None-Match (请求头): 客户端在验证请求中携带此头部,其值为之前收到的 ETag。服务器比较该 ETag 与当前资源的 ETag
    • 如果 ETag 匹配,说明资源未变,返回 304 Not Modified
    • 如果 ETag 不匹配,说明资源已更新,返回 200 OK 和新的资源。

ETag 的优先级高于 Last-Modified。如果请求中同时包含 If-Modified-SinceIf-None-Match,服务器会优先处理 If-None-Match


其他重要头部

  • Expires: HTTP/1.0 的头部,用于指定缓存的过期日期。

    http
    Expires: Wed, 21 Oct 2025 07:28:00 GMT

    由于它依赖于客户端和服务器的时钟同步,Cache-Control: max-age 在 HTTP/1.1 中被推荐作为替代方案。如果 Cache-ControlExpires 同时存在,Cache-Control 的优先级更高。

  • Age: 当资源从代理缓存(如 CDN)中获取时,Age 头部表示该资源在代理缓存中已经存放了多长时间(以秒为单位)。浏览器可以用它来判断缓存是否仍然新鲜。

  • Vary: 用于告知缓存服务器,对于同一个 URL,响应内容可能会根据请求头的不同而有所不同。例如,Vary: Accept-Encoding 表示服务器会根据客户端支持的压缩算法(如 gzip, br) 返回不同的压缩版本。缓存服务器需要根据 Vary 指定的请求头来为同一个 URL 存储多个版本的响应。


缓存决策流程

下面是一个简化的流程图,展示了浏览器如何决定是从缓存加载资源还是向服务器发送请求。

  1. 检查本地缓存:浏览器发起请求时,首先检查是否存在该资源的本地缓存。
    • 无缓存:直接向服务器请求新资源。
  2. 检查缓存是否过期:如果存在缓存,则检查其是否过期(依据 max-ageExpires)。
    • 未过期:直接使用本地缓存的资源(Fresh)。
  3. 进行服务器验证:如果缓存已过期(Stale),则检查是否需要验证(依据 no-cachemust-revalidate)。
    • 需要验证:向服务器发送一个条件请求(携带 If-None-Match/If-Modified-Since 头)。
      • 服务器返回 304 Not Modified:表示资源未改变。浏览器更新缓存的元数据(如新的过期时间),然后使用本地缓存的资源。
      • 服务器返回 200 OK:表示资源已更新。浏览器下载新的资源,并用它替换旧的缓存。
    • 无需验证(例如,没有 must-revalidate 指令):在某些情况下,浏览器可能会直接使用已过期的缓存(例如,在离线模式下)。但在常规在线情况下,通常会重新从服务器获取。

最佳实践

  • 不常变的静态资源 (CSS, JS, 图片):

    • 使用 Cache-Control: max-age=31536000, immutable
    • 文件名中加入哈希值(如 main.a1b2c3d4.css),当文件内容改变时,文件名也会改变,从而绕过缓存。
  • 经常变的静态资源 (HTML):

    • 使用 Cache-Control: no-cache
    • 这会强制浏览器每次都向服务器验证,但如果文件未变,服务器只需返回 304,无需传输整个文件。
  • API 请求:

    • 根据业务需求决定。对于实时性要求高的数据,使用 Cache-Control: no-store
    • 对于可以接受短暂延迟的数据,可以使用 Cache-Control: max-age=<short_duration>, private,例如 max-age=60

通过合理配置这些缓存策略,可以显著提升网站性能和用户体验。