Skip to content

HTTP 缓存

HTTP 缓存是一项关键技术,用于提升网站性能和减轻服务器负载。通过将频繁访问的资源副本存储在更靠近客户端的位置(例如,浏览器缓存、代理缓存),后续对这些资源的请求可以更快地得到响应,而无需联系源服务器。

HTTP 缓存的目标包括:

  • 降低延迟: 从本地缓存提供内容比通过网络获取快得多。
  • 减少网络流量: 避免冗余数据传输,为客户端和服务器节省带宽。
  • 减轻服务器负载: 到达源服务器的请求更少,降低其工作负担。

HTTP/1.1 提供了多种机制来控制缓存行为,主要通过服务器设置的响应头实现。关键概念包括资源新鲜度(freshness)和验证(validation)。

Cache-Control 是 HTTP/1.1 的通用头部字段,是指定请求和响应缓存策略的主要方式。它由逗号分隔的指令列表组成。

指令描述
public表示响应可以被任何缓存存储(浏览器、代理、CDN)。
private表示响应仅供单个用户使用,共享缓存(例如代理)不得缓存。浏览器缓存仍然可以存储它。
no-cache强制缓存在释放缓存副本之前,必须向源服务器提交请求进行验证。这不意味着“不缓存”;而是指“使用前必须重新验证”。
no-store缓存不应存储有关客户端请求或服务器响应的任何内容。这适用于敏感信息。
max-age=<seconds>指定资源被视为新鲜的最长时间(以秒为单位)。相对于请求的时间。
s-maxage=<seconds>类似于 max-age,但仅适用于共享缓存(例如 CDN、代理)。私有缓存会忽略此指令。
must-revalidate告诉缓存必须遵循服务器提供的任何关于资源新鲜度的信息。HTTP 允许缓存在特殊条件下提供陈旧(stale)资源;此指令可阻止这种情况。
proxy-revalidate类似于 must-revalidate,但仅适用于共享缓存。
immutable表示响应体随时间不会改变。资源如果未过期,则不应重新验证。对带有指纹的静态资源(fingerprinted static assets)非常有用。
stale-while-revalidate=<seconds>允许缓存在后台重新验证资源的同时,立即提供一个陈旧的资源。提高了用户感知性能。
stale-if-error=<seconds>如果尝试向源服务器重新验证资源失败(例如,由于网络错误或服务器错误),允许缓存提供一个陈旧的资源。
Example: `Cache-Control: public, max-age=3600, s-maxage=7200, must-revalidate`
指令描述
no-cache客户端需要一个新鲜的响应。缓存必须向源服务器重新验证。
no-store客户端请求缓存不要存储该请求或响应的任何部分。
max-age=<seconds>表示客户端愿意接受一个其 age(自上次成功验证或生成以来经过的时间)不大于指定时间的响应。
min-fresh=<seconds>表示客户端需要一个在未来至少指定秒数内仍然新鲜的响应。
only-if-cached客户端只需要缓存的响应。缓存不应联系源服务器。

Expires 头是一个较旧的 HTTP/1.0 头部,提供一个绝对日期/时间,指示在此之后响应被视为陈旧。

Example: `Expires: Wed, 21 Oct 2025 07:28:00 GMT`

如果同时存在 Expires 和 Cache-Control: max-age(或 s-maxage),则 max-age(或 s-maxage)具有优先权。通常推荐使用 Cache-Control,因为它更灵活。

当缓存资源的新鲜度生命周期过期(例如达到 max-age)或使用 Cache-Control: no-cache 时,缓存需要向源服务器重新验证该资源。这通过带有验证器头的条件请求(conditional requests)完成。

Last-Modified:

服务器可以在其响应中包含 Last-Modified 头,指示资源的最后修改日期。

Example: `Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT`

重新验证时,客户端(或缓存)可以在 If-Modified-Since 请求头中将此日期发送回服务器。如果资源没有改变,服务器会响应 304 Not Modified(未修改)并返回空响应体,从而节省带宽。否则,它会发送 200 OK(成功)以及新资源。

ETag (Entity Tag):

ETag(实体标签)是 Web 服务器为特定版本的资源分配的一个不透明标识符。ETag 提供了更强大的验证机制,特别是在修改日期不够精确或不足以区分时(例如,亚秒级的更改)。

Example: `ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"`

重新验证时,客户端在 If-None-Match 请求头中发送 ETag。如果该 ETag 与服务器当前版本仍匹配,服务器会响应 304 Not Modified。否则,它会发送 200 OK 以及新资源和新的 ETag。

ETag 可以是“强”的(保证字节级的完全一致),也可以是“弱”的(以 W/ 为前缀,表示语义上的等价)。

  • 长期缓存不变资源: 对于版本化的 JavaScript/CSS 文件(例如 app.v1.2.3.js)或带有指纹的图片,使用 Cache-Control: public, max-age=31536000, immutable。immutable 指令告诉浏览器无需重新验证这些资源。
  • 短期缓存或重新验证动态内容: 对于会变化的 HTML 页面或 API 响应,使用较短的 max-age 值,或者使用 no-cache(强制重新验证),或使用 stale-while-revalidate 以获得更好的用户感知性能。
  • 不缓存敏感数据: 对于包含用户隐私数据的响应,使用 Cache-Control: no-store。
  • 使用 Vary 头: 如果响应内容取决于请求头(例如,Accept-Encoding 用于压缩,Accept-Language 用于国际化),请使用 Vary 头来指示缓存存储不同的版本。示例:Vary: Accept-Encoding, Accept-Language。

合理的缓存配置对于构建快速且可伸缩的 Web 应用程序至关重要。它需要仔细考虑资源的特性和用户需求。