跳到主要内容
浏览器
导航
网页浏览器

HTTP 缓存:强缓存与协商缓存

从缓存键与 Vary 出发,梳理 Cache-Control 各指令的语义、Expires 的局限,以及 ETag/If-None-Match 与 Last-Modified/If-Modified-Since 两套协商机制的判定顺序与刷新行为差异。

发布于 4 分钟阅读 1391 字

每次请求一个资源,都要经过一次网络往返。如果同一个资源被反复使用,这些往返大多是冗余的。HTTP 缓存的作用就是让客户端或中间层持有一份可复用的响应副本,在副本仍然可用时直接使用,在不可用时再向服务端确认。

缓存的决策完全由响应头驱动,参与决策的每一层——浏览器私有缓存、代理缓存、CDN 节点——各自独立判断。因此同一组响应头在不同层级的实际效果可能并不一致。

缓存键与 Vary

缓存条目的查找依赖缓存键。多数实现以一个「方法 + URL」组合作为主键,因此同一个 URL 的不同表示需要额外机制区分。Vary 响应头用于把若干请求头纳入缓存键:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: br
Vary: Accept-Encoding, Accept-Language
Cache-Control: max-age=600

上例中,Accept-Encoding 或 Accept-Language 不同的请求会分别形成独立的缓存条目。Vary: * 表示该响应不可被复用。把过多请求头写进 Vary 会降低命中率,因为它把一个 URL 拆成了多个条目。

强缓存:Cache-Control 指令

强缓存指在不联系服务端的前提下直接使用副本。判定依据集中在 Cache-Control:

指令语义
max-age=N副本在 N 秒内视为新鲜,可直接使用
s-maxage=N覆盖 max-age,只对共享缓存生效
no-cache副本可以存储,但每次使用前必须向服务端校验
no-store不允许以任何形式存储该响应
private只允许私有缓存(如浏览器)存储
public允许共享缓存存储,即使响应带认证信息
must-revalidate副本过期后不允许在断网等情况下继续使用
immutable声明资源在有效期内不会变化,跳过校验

需要区分 no-cache 与 no-store:前者仍会保存副本,只是每次使用都要求确认;后者则不写入任何存储。must-revalidate 针对的是副本过期后的行为,它禁止以「陈旧内容继续展示」的方式容错。

HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
ETag: "a1b2c3"

Expires 的局限

Expires 是 HTTP/1.0 引入的过期字段,取值为一个固定的日历时间:

Expires: Wed, 21 Oct 2026 07:28:00 GMT

它的判断依赖客户端的本地时钟。客户端时间若与服务器时间偏差较大,过期时间就会提前或推迟生效,实际行为变得不可预期。当 Cache-Control 中出现 max-age 时,max-age 优先于 Expires,后者通常只作为兼容旧客户端的兜底。

协商缓存

副本过期后,并非立刻要重新下载整个响应,而是可以带上条件请求头向服务端确认副本是否仍然有效。若服务端判定没有变化,返回不带响应体的 304,客户端继续使用本地副本。这套机制涉及两组字段。

ETag 与 If-None-Match

服务端为响应计算一个标识符,客户端在后续请求中通过 If-None-Match 回传。标识符变化即视为内容变化。标识符可以带 W/ 前缀表示弱校验,弱校验只要求语义等价。

Last-Modified 与 If-Modified-Since

服务端给出资源的修改时间,客户端通过 If-Modified-Since 回传。它的精度通常只到秒,因此一秒内的多次修改可能无法区分;若资源由多台机器生成,修改时间还可能不一致。

GET /style.css HTTP/1.1
If-None-Match: "a1b2c3"

HTTP/1.1 304 Not Modified
ETag: "a1b2c3"
Cache-Control: max-age=60

两组字段同时存在时,ETag 系列优先参与判定。各实现对 ETag 的生成策略不同,有的基于内容摘要,有的把 inode 与修改时间组合,因此同一资源在不同服务器上得到的标识符并不通用。

判定顺序与刷新行为

一个请求到达缓存层后,判断过程可以概括为:

  1. 查找缓存键对应的副本,若无则发起完整请求
  2. 若副本仍在有效期内,直接使用,不产生网络请求(强缓存)
  3. 若副本已过期,带上条件请求头发起校验请求(协商缓存)
  4. 收到 304 则复用副本并刷新其有效期;否则用新的响应替换副本
触发方式是否使用未过期的副本是否发起校验请求
地址栏输入并回车通常使用副本过期时才发起
页面内跳转 / 前进后退通常使用副本过期时才发起
重新加载(刷新)不使用通常会发起
强制重新加载不使用不发起,直接取新响应

上表的差异来自浏览器对「重新加载」的处理:重新加载时,浏览器会给请求附加等价于 max-age=0 的条件,使副本被视为立即过期,从而强制走一次校验;强制重新加载则进一步跳过校验,并让新响应覆盖已有副本。这两条规则也是排查「改了文件却不生效」类问题的关键,可与 HTTP/2 多路复用 中对连接复用的描述结合起来看。

小结

强缓存的判定只看有效期,不产生网络往返;协商缓存的判定需要一次轻量校验,但能避免重传响应体。两者串联成一条顺序固定的判定链,而链路是否生效取决于响应头是否正确、缓存键是否被 Vary 切割、以及请求由哪种方式触发。

网页浏览器 · 相关文章