HTTP 与缓存

HTTP 与缓存——怎样从协议语义理解缓存,而不是把缓存只看成浏览器会不会复用资源。重点包括 freshness、revalidation、Cache-Control、ETag 和共享缓存边界。

HTTP 与缓存

为什么前端工程师必须把缓存当成协议问题来学

很多人第一次学缓存,是从“资源第二次加载更快”开始的。

这个入口没有问题。

但如果只停在这里,后面就会越来越混乱。

因为真实系统里的缓存,不只是浏览器里的一个开关。

它同时牵涉:

  • HTTP 响应头
  • 浏览器缓存
  • CDN 和代理缓存
  • 资源版本控制
  • 动态数据的新鲜度
  • revalidation

所以更稳妥的理解是:

缓存首先是协议语义问题,然后才是性能收益问题。

先把“缓存”讲准确

缓存可以先理解成:

在未来可能重复使用的数据上,提前保存一个可复用副本,以减少重复获取成本。

但这句话还不够。

在 Web 里,更关键的问题是:

  • 谁在缓存
  • 缓存多久
  • 是否允许复用
  • 什么时候必须重新验证

这几个问题,才真正决定缓存行为。

HTTP 缓存到底在解决什么

它解决的不是“快一点”这么简单。

更完整地说,它在平衡三件事:

  • 响应速度
  • 网络与服务器成本
  • 数据新鲜度

如果只追求快,缓存可能会过期太久。

如果只追求最新,缓存收益又会非常低。

所以 HTTP 缓存本质上是一个受约束的交换。

浏览器缓存、共享缓存、应用数据缓存不是一回事

这是很常见的混淆点。

浏览器缓存

终端用户浏览器本地保存的响应副本。

共享缓存

通常是 CDN、反向代理或中间缓存节点持有的副本。

应用数据缓存

比如 React Query、SWR 或业务层自己维护的数据缓存。

这三种缓存可能同时存在。

如果不区分层次,讨论很容易乱掉。

Cache-Control 为什么是最核心的入口

因为它直接告诉缓存系统:

这个响应该怎么被缓存和复用。

这不是唯一相关头部。

但通常是最关键的一组指令。

max-age 可以怎么理解

最简单的理解是:

在这个时间窗口内,缓存副本可以被视为新鲜的。

这里最重要的词是“新鲜”。

因为缓存并不是只有“有”或“没有”。

它还有“新鲜”和“陈旧”的状态差别。

什么叫 freshness

freshness 就是:

一个缓存响应是否还能直接使用,而不先去问源站“它还有效吗”。

如果一个响应仍然 fresh,浏览器或中间缓存通常可以直接复用它。

如果它 stale 了,就要进入 revalidation 逻辑。

revalidation 是什么

revalidation 可以简单理解成:

缓存副本虽然还在,但不再被直接信任,于是客户端带着某些校验信息去向源站确认它是否还能继续复用。

这是 HTTP 缓存里非常关键的一层。

因为很多系统真正高效的地方,不是完全不请求。

而是发出成本更低的验证请求。

ETagLast-Modified 应该怎么讲

它们都属于 validator。

也就是缓存验证器。

ETag

服务端给资源生成的标识。

客户端下次请求时可以带上 If-None-Match

如果服务端确认内容没变,就返回 304 Not Modified

Last-Modified

表示资源最后修改时间。

客户端可以带 If-Modified-Since 去验证。

ETag 比,它通常更粗粒度。

304 Not Modified 为什么重要

因为它体现了 HTTP 缓存真正的聪明之处:

不是简单地“要么全下载,要么不下载”。

而是允许你在确认资源没变时,复用已有响应体。

这能同时兼顾:

  • 新鲜度
  • 带宽
  • 响应效率

no-cache 经常被误解成“不缓存”

这是前端里非常高频的误解。

更准确地说,no-cache 通常不是“绝对不存”。

它更接近:

允许缓存存下来,但在复用前必须重新验证。

真正更接近“不要存”的是:

  • no-store

这两个不能混着讲。

no-store 为什么更严格

它强调的是:

不要把响应存进任何缓存。

这通常更适合:

  • 高敏感信息
  • 不应被本地或中间层持久化的响应

所以如果你需要表达“绝对不该保留”,更应该想到 no-store,不是 no-cache

publicprivate 为什么重要

因为不是所有缓存都只在浏览器本地发生。

public 允许共享缓存复用。

private 则更偏向仅终端用户私有缓存可用。

这在登录态页面、个性化资源和 CDN 配置里尤其关键。

为什么静态资源和接口数据的缓存策略经常不同

因为它们的变化模式和业务风险不同。

静态资源

常见策略是:

  • 文件名带 hash
  • 长时间缓存
  • 内容变了就换 URL

这样缓存命中高,失效逻辑也简单。

接口数据

通常更强调:

  • 新鲜度
  • 条件验证
  • 权限和个性化
  • 局部更新

所以接口缓存更像一套动态平衡,而不是一把锁死的长期缓存。

为什么“文件名带 hash”这么常见

因为这是一种非常实用的缓存失效策略。

资源内容一变,URL 跟着变。

这样老缓存就不会误命中新内容。

对于 JS、CSS、图片这类静态资源,它比“强行缩短缓存时间”通常更高效。

CDN 缓存为什么不能和浏览器缓存混为一谈

CDN 处在共享缓存层。

它面对的是:

  • 多用户
  • 多地区
  • 多边缘节点

所以它的缓存控制不仅影响单个用户体验,也影响整体分发成本。

同一个 Cache-Control,在浏览器和 CDN 上可能都生效,但讨论语境不一样。

为什么缓存总会和个性化、鉴权冲突

因为缓存喜欢“大家都能复用的响应”。

而个性化和鉴权经常意味着:

  • 响应因用户而异
  • 某些内容不应该被共享缓存复用

所以一旦响应和登录态、权限、地区、实验分流绑定,缓存策略就必须更谨慎。

Vary 为什么值得知道

Vary 可以理解成:

告诉缓存系统,响应不仅由 URL 决定,还受某些请求头影响。

这在内容协商、压缩或语言版本里很重要。

如果该 Vary 的地方没写,缓存可能会错把 A 用户的变体给到 B 用户。

前端为什么不能只把缓存交给后端和 CDN 团队

因为前端直接影响:

  • 资源命名策略
  • 构建产物 hash
  • 请求方式
  • 页面更新时机
  • 本地数据缓存策略

如果前端完全不理解 HTTP 缓存,就很容易出现:

  • 刚发版用户看不到新资源
  • 某些接口一直读到旧数据
  • 本地数据缓存和网络缓存互相打架

HTTP 缓存和 React Query 这类数据缓存是什么关系

两者不是替代关系。

HTTP 缓存更偏协议层。

React Query 这类工具更偏应用层数据生命周期管理。

它们可以叠加。

但不应该混成一个概念。

更成熟的讲法是:

  • HTTP 缓存控制网络响应复用
  • 应用层缓存控制组件消费和状态同步

stale-while-revalidate 为什么近几年经常被提到

因为它很好地体现了现代缓存策略的一种思路:

允许先用一个可能略旧但仍可接受的结果,同时在后台更新。

这在体验上很有吸引力。

但它也意味着团队必须接受:

某些时刻用户看到的不是绝对最新值。

所以它不是“总是更好”。

它是一种明确 trade-off。

常见误区

1. 把 no-cache 说成“不要缓存”

这是经典误区。

2. 以为缓存只发生在浏览器

现实里共享缓存和边缘缓存非常重要。

3. 以为缓存命中越高越好

命中率高不一定代表策略正确。

如果旧数据风险高,命中率高也可能是问题。

4. 把静态资源缓存和接口缓存用同一套直觉处理

这通常会出事。

中国互联网语境下的常见缓存难点

比较常见的是:

  • 多 CDN、多网关、灰度链路并存
  • 活动页和交易页节奏都很快
  • 资源发版频繁
  • WebView 和浏览器环境并存

这会让缓存问题更容易表现成:

  • 部分用户拿到旧资源
  • 某些环境刷新后行为不一致
  • CDN 和浏览器缓存交织导致排障困难

海外产品语境里,为什么更常把缓存和协议语义放在一起讲

很多海外文档和工程文章会更强调:

  • HTTP 语义
  • shared cache
  • validator
  • stale 策略

这对前端是好事。

因为它会逼你从平台能力而不是“经验偏方”去理解缓存。

这类主题为什么很适合做表达训练

因为它太容易被讲成:

  • 缓存可以让页面更快

这不够。

更好的表达应该继续说明:

  • 缓存是在速度和新鲜度之间做平衡
  • freshness 和 revalidation 是关键概念
  • no-cacheno-store 不能混讲
  • 静态资源和接口数据策略通常不同

建议实践

实践 1:做一次资源缓存头实验

练什么:

理解 Cache-Control 与资源更新的关系。

最小交付物:

一个带 hash 资源和不同缓存头的最小 demo。

验收标准:

  • 能观察到首次请求、命中缓存和重新验证的差异
  • 能解释为什么改文件名能改变缓存行为

常见误区:

  • 只刷新页面看快不快,不看响应头和状态码

实践 2:抓包观察 ETag / 304

练什么:

建立 validator 和 revalidation 直觉。

最小交付物:

一次浏览器 Network 面板记录。

验收标准:

  • 能解释 If-None-Match304 的作用
  • 能区分“没请求”与“发起了验证请求”

常见误区:

  • 304 误解成“完全没走网络”

实践 3:对比静态资源和接口缓存

练什么:

理解不同对象的缓存策略为什么不同。

最小交付物:

一份静态资源与 API 响应缓存策略对照表。

验收标准:

  • 能说明各自重点约束
  • 能说明为什么不能一刀切

常见误区:

  • 只拿“越久越好”做判断

延展阅读