协商缓存(也称为条件请求缓存)是 HTTP 缓存机制的一种,用于在资源过期后(强缓存失效时),通过向服务器发送验证请求,确认资源是否已被修改。若资源未修改,服务器返回 304 Not Modified,浏览器继续使用本地缓存;若资源已修改,则返回新资源。这种方式减少了不必要的网络传输,优化性能。
一、协商缓存的核心流程
-
浏览器发送请求:
当强缓存(如Cache-Control: max-age)过期后,浏览器向服务器发起请求,携带验证字段(如If-Modified-Since或If-None-Match)。 -
服务器验证资源:
服务器根据请求头中的验证字段,判断资源是否修改:- 未修改 → 返回
304 Not Modified(不携带资源内容)。 - 已修改 → 返回
200 OK和新资源。
- 未修改 → 返回
-
浏览器处理响应:
- 收到
304→ 复用本地缓存。 - 收到
200→ 使用新资源并更新缓存。
- 收到
二、协商缓存的验证字段
1. 基于修改时间:Last-Modified 与 If-Modified-Since
- 响应头:
Last-Modified: <GMT 时间>
服务器告知资源的最后修改时间(如Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT)。 - 请求头:
If-Modified-Since: <GMT 时间>
浏览器发送资源上次的修改时间,询问是否变化。
局限性:
- 时间精度为秒级,若资源在 1 秒内多次修改可能无法检测。
- 文件内容未变但修改时间更新(如重新生成文件)会导致误判。
2. 基于内容标识:ETag 与 If-None-Match
- 响应头:
ETag: <唯一标识>
服务器生成资源的唯一标识(如哈希值ETag: "33a64df551425fcc55e4d42a148795d9")。 - 请求头:
If-None-Match: <ETag 值>
浏览器发送缓存的ETag,询问是否匹配。
优势:
- 精确检测内容变化(即使修改时间未变)。
- 支持弱验证(
W/前缀,如ETag: W/"0815"),允许内容语义不变时复用缓存。
三、协商缓存 vs 强缓存
| 对比维度 | 强缓存 | 协商缓存 |
|---|---|---|
| 触发条件 | 缓存未过期 | 缓存已过期 |
| HTTP 状态码 | 200 (from disk cache) | 304 Not Modified |
| 网络请求 | 无请求 | 有请求(验证缓存有效性) |
| 关键头部字段 | Cache-Control、Expires | Last-Modified、ETag |
| 性能影响 | 最佳(直接读取本地缓存) | 次优(需与服务器通信验证) |
四、实际场景示例
1. 首次请求资源
# 请求
GET /style.css HTTP/1.1
# 响应
HTTP/1.1 200 OK
Cache-Control: max-age=3600
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
ETag: "33a64df551425fcc55e4d42a148795d9"
2. 缓存过期后再次请求
# 请求(携带验证字段)
GET /style.css HTTP/1.1
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT
If-None-Match: "33a64df551425fcc55e4d42a148795d9"
# 响应(资源未修改)
HTTP/1.1 304 Not Modified
3. 资源已修改
# 响应(返回新资源)
HTTP/1.1 200 OK
Cache-Control: max-age=3600
Last-Modified: Wed, 21 Oct 2023 08:00:00 GMT
ETag: "5c7f8b7e5d9a4b3d6e7f8c9a0b1c2d3e"
五、如何配置协商缓存
1. 服务器端设置
-
Apache:默认启用
Last-Modified和ETag。 -
Nginx:通过配置自动生成
ETag:etag on; -
后端代码(如 Node.js):
app.get("/file", (req, res) => { const fileContent = readFileSync("file.txt"); const etag = generateHash(fileContent); // 生成 ETag res.setHeader("ETag", etag); res.send(fileContent); });
2. 禁用协商缓存
# 强制要求每次验证(优先级高于强缓存)
Cache-Control: no-cache
六、最佳实践
-
强缓存 + 协商缓存结合:
- 对静态资源(如 CSS、JS)设置较长
max-age(如 1 年),通过文件名哈希解决更新问题。 - 资源过期后通过协商缓存验证。
- 对静态资源(如 CSS、JS)设置较长
-
优先使用
ETag:
避免Last-Modified因时间精度或文件元数据变化导致的误判。 -
避免过度协商:
对频繁变动的接口数据禁用缓存(Cache-Control: no-store)。
通过合理使用协商缓存,可以显著减少网络带宽消耗,提升用户体验,尤其适用于内容更新不频繁但需要长期缓存的场景。