首页/网络/协议速查/协商缓存(再验证)
HTTP 头与缓存70 条

协商缓存(再验证)

本地副本过期后带校验值回源:资源未变则 304(省带宽),变了则 200 返回新内容。

协议速查网络更新于 2026-10-13

协商缓存是什么

强缓存过期后,客户端不立刻重新下载整个资源,而是带着校验值回源问一句「我这份还能不能用」:资源没变,服务器回 304 Not Modified(没有响应体);变了则回 200 并带新内容。校验值有两个来源:ETag 是内容指纹(强校验),Last-Modified 是秒级修改时间(弱校验)。它解决的是「既想复用本地副本、又怕拿到旧数据」的矛盾。

基本信息

适用平台浏览器、Nginx/Apache、CDN 边缘节点等所有 HTTP 缓存
请求头If-None-Match(配 ETag)、If-Modified-Since(配 Last-Modified)
响应304 Not Modified 或 200 OK
前提缓存条目已过期(stale)但允许再验证
规范RFC 9111 §4.3、RFC 9110 §13

典型使用场景

  • 静态资源加长 max-age 后需要兜底校验,避免内容更新被缓存挡住
  • 体积大但更新频率低的 HTML 页与 JSON 接口,省掉重复传输的流量
  • CDN 回源:边缘节点对源站做再验证,降低回源带宽
  • 移动端弱网环境:304 只花一个来回,比重新下载整个响应体便宜得多
  • 作为强缓存的降级策略,在无法预知内容是否变更时使用

观察协商过程

观察协商过程
curl -sI https://www.example.com/ | grep -i etag          # 第一次:记下 ETag
curl -sI -H 'If-None-Match: "abc123"' https://www.example.com/ | head -1   # 期望 304
curl -sI -H 'If-Modified-Since: Sat, 11 Oct 2026 12:00:00 GMT' https://www.example.com/ | head -1  # 时间校验
curl -sv https://www.example.com/ -o /dev/null 2>&1 | grep -E '^(<|>) (If-|ETag|Last-Modified)'   # 看完整往返

三种返回对照

返回含义客户端动作
304 Not Modified校验值命中,本地副本仍有效复用本地副本,并按新响应头更新新鲜期
200 OK资源已变更或服务端不做条件请求用响应体替换本地副本
412 Precondition FailedIf-Match / If-Unmodified-Since 不满足并发写被拒,应先取最新版本再提交
304 可以携带新的 Cache-Control、ETag 等头,客户端不能因为它没有 body 就整条忽略——这些头才是刷新新鲜期的依据。
协商缓存每次仍至少多一个 RTT。高频小接口更适合「短 max-age 强缓存 + stale-while-revalidate 后台刷新」,而不是每次都再验证。

常见误用对照

  • 把 304 当成「缓存已过期」:它恰恰说明本地副本仍然可用,只是顺便刷新了新鲜期
  • 在 ETag 里混入用户 ID、时间戳等个性化信息,导致不同用户之间完全无法互相命中,缓存形同虚设
  • 同时把强缓存与协商缓存都配成 no-store,等于主动放弃全部缓存能力
  • 只用 If-Modified-Since 做写操作的并发控制,秒级精度会漏掉同一秒内的连写冲突

易错点

  • 服务端只发 Last-Modified 不发 ETag:一秒内的多次修改无法区分,内容改了客户端仍可能拿到旧副本
  • Nginx 默认用 inode+mtime 生成 ETag,多台源站间天然不一致,集群必须改用内容摘要,否则 304 命中率会剧烈抖动
  • 中间代理丢弃或改写 If-None-Match,再验证悄悄退化成全量下载,只有看访问日志里的状态码分布才能发现
  • 给 304 响应塞响应体或改 Content-Length,部分客户端会解析失败甚至卡住连接

数据来源与参考

  • RFC 9111 §4.3(再验证)
  • RFC 9110 §13(条件请求)

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌