协商缓存
协商缓存:资源过期后带验证信息去问服务器,没变返回 304,变了返回新内容。
#tech / dev / frontend
#type / concept
#status / evergreen
#platform / browser
#tech / network
[!info] related notes
- 所属 MOC: 浏览器缓存体系
- 分类总览: 浏览器缓存分类与 304 / memory cache / disk cache
- 前置概念: 强缓存
- 关联概念: [[http-cache-storage|浏览器内部缓存存储]], Vary 与缓存键
协商缓存
协商缓存:资源过期后,带验证信息去问服务器是否还能用。
触发条件
强缓存不命中(max-age 过期)后,进入协商缓存流程。
两种验证方式
1. ETag / If-None-Match
# 服务器首次响应
ETag: "abc123"
# 浏览器下次请求
If-None-Match: "abc123"
# 服务器响应
304 Not Modified # ETag 匹配,资源没变
2. Last-Modified / If-Modified-Since
# 服务器首次响应
Last-Modified: Tue, 07 Apr 2026 10:00:00 GMT
# 浏览器下次请求
If-Modified-Since: Tue, 07 Apr 2026 10:00:00 GMT
# 服务器响应
304 Not Modified # 时间匹配,资源没变
Strong vs Weak ETag
ETag 有两种强度:
Strong ETag
ETag: "abc123"
- 保证逐字节完全一致
- 可用于
If-None-Match(缓存验证)和If-Range(断点续传) - 服务器只有在资源内容完全不变时才应返回相同的 strong ETag
Weak ETag
ETag: W/"abc123"
- 保证语义等价,但不一定逐字节相同
- 只能用于
If-None-Match,不能用于If-Range - 适用场景:服务器对响应做了 gzip 压缩、去除了空白字符等不影响语义的变换
选择建议
| 场景 | 用哪种 |
|---|---|
| 静态文件哈希 | Strong ETag |
| 动态生成内容 | Weak ETag(语义等价即可) |
| 断点续传 / Range 请求 | 必须 Strong ETag |
| 内容可能有微小格式差异 | Weak ETag |
ETag vs Last-Modified
| 对比 | ETag | Last-Modified |
|---|---|---|
| 精度 | 基于内容 hash,更精确 | 精确到秒 |
| 成本 | 服务器需维护/计算 | 低 |
| 优先级 | 更高(同时存在时) | - |
ETag 更精确:因为是版本语义,不只是时间语义。
条件请求:PUT / DELETE 中的验证
条件请求头不仅用于 GET 缓存验证,还用于乐观并发控制。
If-Match(PUT / DELETE)
PUT /api/resource/123
If-Match: "abc123"
- 含义:只有当资源当前 ETag 是
"abc123"时才执行更新 - 如果 ETag 不匹配 → 服务器返回
412 Precondition Failed - 用途:防止”最后写入者覆盖”问题(Lost Update)
If-None-Match: *(PUT)
PUT /api/resource/new
If-None-Match: *
- 含义:只有当资源不存在时才创建
- 如果已存在 →
412 Precondition Failed - 用途:REST API 的”仅创建不覆盖”语义
对比
| 头部 | 方法 | 语义 |
|---|---|---|
If-None-Match | GET | 资源变了才发给我(缓存验证) |
If-Match | PUT/DELETE | 资源没变才能改(并发控制) |
If-None-Match: * | PUT | 资源不存在才能创建 |
边界情况
If-Modified-Since 不能用于 Range 请求
RFC 9110 规定:If-Modified-Since 不得用于字节范围请求。断点续传应使用 If-Range 头部配合 strong ETag。
秒级精度缺失
Last-Modified 精确到秒。如果同一秒内修改了两次,第二次修改无法被检测到。If-Unmodified-Since 也有同样的限制。
Last-Modified 缺失
如果服务器从未返回 Last-Modified,浏览器无法使用 If-Modified-Since。此时只能依赖 ETag。
304 vs 200 决策
服务器何时返回 304,何时返回 200:
| 条件 | 响应 |
|---|---|
| 验证器匹配 + 缓存可用 | 304 Not Modified |
| 验证器不匹配 | 200 OK + 新内容 |
| 无法确定等价性 | 200 OK + 新内容 |
| 响应头变化影响缓存 | 200 OK + 新内容 |
304 可以更新缓存头
RFC 9111 Section 4.3.4:304 响应可以携带新的 Cache-Control、Expires、Set-Cookie 等头部来更新已缓存的元数据,无需重新传输资源体。
# 304 响应可以包含
304 Not Modified
Cache-Control: max-age=7200 # 更新缓存有效期
ETag: "def456" # 更新 ETag
完整流程
浏览器请求资源
↓
检查强缓存(max-age)
↓
[过期]
↓
带 ETag/Last-Modified 发协商请求
↓
服务器判断
↓
304 → 继续用本地副本(省流量)
200 → 下载新内容,更新缓存
性能价值
虽然发了请求,但:
- 省流量:304 不带响应体
- 省带宽:只传验证信息
- 比直接请求快:服务器处理比完整响应快