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 缓存链包含多个层级,每个层级有不同的职责和策略:

私有缓存 (Private Cache):
- 位于客户端浏览器中
- 仅服务于单个用户
- 可以存储用户特定的内容(如认证后的响应)
共享缓存 (Shared Cache):
- 包括 CDN 缓存和代理缓存
- 服务于多个用户
- 需要特别注意内容隐私和安全性
源服务器 (Origin Server):
- 内容的原始提供者
- 通过 HTTP 头定义缓存策略
2.2 新鲜度判断机制
缓存的核心决策逻辑围绕"新鲜度" (freshness) 展开:
响应年龄 (Age):
- 自响应生成或最后验证以来经过的时间
- 浏览器会累加中间缓存返回的
Age头
新鲜度时间线 (Freshness Lifetime):
- 超过此时间后响应被视为过期 (stale)
优先级判断顺序:
Cache-Control: max-age=<秒数>Expires和Date头之间的时间差- 基于
Last-Modified的启发式计算 - 对于共享缓存,
s-maxage优先级最高
3. 缓存决策流程
当缓存收到请求时,遵循以下决策逻辑:

3.1 验证机制
当响应过期后,缓存并非必须丢弃,而是可以通过条件请求进行验证:
验证标识符:
Last-Modified: <日期>→If-Modified-Since: <日期>ETag: "<值>"→If-None-Match: "<值>"
验证响应:
200 OK:服务器返回新内容304 Not Modified:缓存内容可继续使用
3.2 过期响应处理
某些情况下,缓存可以返回过期响应:
- 与源服务器连接失败
- 服务器返回 5xx 错误
- 配置了
stale-while-revalidate或stale-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 响应
- 当时的软刷新行为会重新验证所有资源
解决方案演化:
- Firefox 方案:实现
immutable指令(RFC 8246) - Chrome 方案:改变软刷新行为(仅验证主资源)
- Safari 方案:跟随 Chrome 的新行为
当前状态:
- Firefox 和 Safari 支持
immutable - Chrome 认为新的刷新行为已足够,无需额外指令
- 指令处于尴尬境地,实际应用有限
7. 认证请求的缓存处理
这是一个容易出错的复杂领域:
7.1 默认规则
共享缓存禁止存储包含 Authorization 头的请求的响应。
7.2 例外情况
以下三个指令可以允许共享缓存存储认证响应:
public:明确允许共享缓存s-maxage=<N>:为共享缓存定义策略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, immutable9.2 HTML 文档
Cache-Control: no-cache或配合 ETag 验证:
Cache-Control: max-age=0, must-revalidate
ETag: "<hash>"9.3 API 响应
Cache-Control: private, no-cache9.4 CDN 优化
Cache-Control: public, max-age=600, s-maxage=3600, stale-while-revalidate=3009.5 敏感数据
Cache-Control: private, no-store, must-understand, no-store10. 参考资料
- RFC 9111 (2022) - HTTP Caching
- RFC 8246 (2017) - HTTP Immutable Responses
- RFC 5861 (2010) - HTTP Cache-Control Extensions for Stale Content
- Dan Cătălin Burzo - HTTP caching, a refresher
- Mark Nottingham - Caching Tutorial for Web Authors and Webmasters (1998—)
- Jake Archibald - Caching best practices & max-age gotchas (2016)
- Harry Roberts - Cache control for civilians (2019–2025)
- MDN - Web Caching
文档生成时间:2026-01-14
基于原始文章发布时间:2025-12-22