HTTP 缓存机制深度解析:基于 RFC 9111 的技术分析

摘要

本文基于 Dan Cătălin Burzo 的技术文章 "HTTP caching, a refresher" 和 RFC 9111 (2022) 标准,对 HTTP 缓存机制进行第一性原理分析。HTTP 缓存是 Web 性能优化的核心技术,涉及从浏览器私有缓存到 CDN 共享缓存的多层架构。本文系统分析了缓存系统的组成要素、决策流程、关键指令及其交互关系。

1. 核心问题定义

HTTP 缓存需要解决的核心问题是:

  • 减少网络传输开销:避免重复传输相同内容
  • 降低服务器负载:减少源服务器请求数量
  • 提升用户体验:加快内容加载速度
  • 保证内容一致性:确保用户获取最新或可接受的内容版本

2. 系统组成要素分析

2.1 缓存架构层级

HTTP 缓存链包含多个层级,每个层级有不同的职责和策略:

HTTP 缓存链架构

私有缓存 (Private Cache)

  • 位于客户端浏览器中
  • 仅服务于单个用户
  • 可以存储用户特定的内容(如认证后的响应)

共享缓存 (Shared Cache)

  • 包括 CDN 缓存和代理缓存
  • 服务于多个用户
  • 需要特别注意内容隐私和安全性

源服务器 (Origin Server)

  • 内容的原始提供者
  • 通过 HTTP 头定义缓存策略

2.2 新鲜度判断机制

缓存的核心决策逻辑围绕"新鲜度" (freshness) 展开:

响应年龄 (Age)

  • 自响应生成或最后验证以来经过的时间
  • 浏览器会累加中间缓存返回的 Age

新鲜度时间线 (Freshness Lifetime)

  • 超过此时间后响应被视为过期 (stale)
  • 优先级判断顺序:

    1. Cache-Control: max-age=<秒数>
    2. ExpiresDate 头之间的时间差
    3. 基于 Last-Modified 的启发式计算
    4. 对于共享缓存,s-maxage 优先级最高

3. 缓存决策流程

当缓存收到请求时,遵循以下决策逻辑:

缓存决策流程图

3.1 验证机制

当响应过期后,缓存并非必须丢弃,而是可以通过条件请求进行验证:

验证标识符

  • Last-Modified: <日期>If-Modified-Since: <日期>
  • ETag: "<值>"If-None-Match: "<值>"

验证响应

  • 200 OK:服务器返回新内容
  • 304 Not Modified:缓存内容可继续使用

3.2 过期响应处理

某些情况下,缓存可以返回过期响应:

  • 与源服务器连接失败
  • 服务器返回 5xx 错误
  • 配置了 stale-while-revalidatestale-if-error 指令

4. Cache-Control 响应指令分析

4.1 基础指令

指令作用适用场景
max-age=<N>定义新鲜度时间线(秒)静态资源缓存控制
must-revalidate过期后必须验证成功才能使用需要强一致性的内容
no-cache每次使用前都必须验证动态内容或敏感数据
no-store完全禁止缓存存储敏感个人信息
private仅允许私有缓存用户特定内容
public允许共享缓存公共静态资源
s-maxage=<N>共享缓存的新鲜度时间线CDN 策略控制
proxy-revalidate共享缓存的 must-revalidate代理缓存控制
no-transform禁止中间人转换内容需要原始格式的资源
must-understand不理解状态码时不缓存面向未来的兼容性

4.2 扩展指令 (RFC 5861)

指令作用浏览器支持
stale-while-revalidate=<N>允许在过期 N 秒内使用过期响应,同时后台验证良好
stale-if-error=<N>验证失败时允许使用过期 N 秒内的响应较差

5. Cache-Control 请求指令分析

请求指令代表客户端对响应新鲜度的偏好:

指令作用
max-age=<N>接受年龄不超过 N 秒的响应
max-stale=<N>接受过期不超过 N 秒的响应
min-fresh=<N>偏好至少还有 N 秒新鲜度的响应
no-cache请求缓存先验证再使用
no-store请求缓存不存储此次请求和响应
only-if-cached仅使用缓存,否则返回 504

6. 浏览器刷新机制

6.1 软刷新 (Soft Reload)

  • 触发方式:Ctrl+R (Windows/Linux) 或 Command+R (macOS)
  • 行为

    • Firefox/Chrome:重新验证主资源,子资源按缓存策略加载
    • Safari:无条件请求主资源,子资源按缓存策略加载

6.2 硬刷新 (Hard Reload)

  • 触发方式:Ctrl+Shift+R 或 Command+Shift+R (Safari: Command+Option+R)
  • 行为:所有资源都发送 Cache-Control: no-cache 请求

6.3 immutable 指令的演进史

immutable 指令的诞生是一个有趣的协议设计案例:

问题背景(约 2015 年):

  • Facebook 发现用户刷新页面时,长期存活的脚本和样式表会产生大量 304 响应
  • 当时的软刷新行为会重新验证所有资源

解决方案演化

  1. Firefox 方案:实现 immutable 指令(RFC 8246)
  2. Chrome 方案:改变软刷新行为(仅验证主资源)
  3. Safari 方案:跟随 Chrome 的新行为

当前状态

  • Firefox 和 Safari 支持 immutable
  • Chrome 认为新的刷新行为已足够,无需额外指令
  • 指令处于尴尬境地,实际应用有限

7. 认证请求的缓存处理

这是一个容易出错的复杂领域:

7.1 默认规则

共享缓存禁止存储包含 Authorization 头的请求的响应。

7.2 例外情况

以下三个指令可以允许共享缓存存储认证响应:

  1. public:明确允许共享缓存
  2. s-maxage=<N>:为共享缓存定义策略
  3. must-revalidate:带有强验证要求

7.3 防护机制

private 指令可以防止其他指令意外使认证响应可被共享缓存。

8. 关键技术洞察

8.1 协议演进的连续性

  • RFC 2616 (1999) → RFC 7234 (2014) → RFC 9111 (2022)
  • 协议更新解决实际部署问题(如 immutable 的提出)
  • 浏览器实现与协议标准相互影响

8.2 实现差异的挑战

不同指南和博客文章的建议不仅是对规范的解读,还包含了对不合规或过时缓存/中间件的防御措施。这说明了实际部署的复杂性。

8.3 缓存不等于存储

no-store 的语义说明:

  • "MUST NOT store" 指不得有意存储到非易失性存储
  • 需要尽力从易失性存储中删除
  • 不是可靠的隐私保护机制
  • 恶意或被攻破的缓存可能不遵守此指令

9. 实践建议

9.1 静态资源

Cache-Control: public, max-age=31536000, immutable

9.2 HTML 文档

Cache-Control: no-cache

或配合 ETag 验证:

Cache-Control: max-age=0, must-revalidate
ETag: "<hash>"

9.3 API 响应

Cache-Control: private, no-cache

9.4 CDN 优化

Cache-Control: public, max-age=600, s-maxage=3600, stale-while-revalidate=300

9.5 敏感数据

Cache-Control: private, no-store, must-understand, no-store

10. 参考资料

  1. RFC 9111 (2022) - HTTP Caching
  2. RFC 8246 (2017) - HTTP Immutable Responses
  3. RFC 5861 (2010) - HTTP Cache-Control Extensions for Stale Content
  4. Dan Cătălin Burzo - HTTP caching, a refresher
  5. Mark Nottingham - Caching Tutorial for Web Authors and Webmasters (1998—)
  6. Jake Archibald - Caching best practices & max-age gotchas (2016)
  7. Harry Roberts - Cache control for civilians (2019–2025)
  8. MDN - Web Caching

文档生成时间:2026-01-14
基于原始文章发布时间:2025-12-22

最后修改:2026 年 01 月 14 日
如果觉得我的文章对你有用,请随意赞赏