协商缓存-HTTP缓存机制

279 阅读4分钟

协商缓存(也称为条件请求缓存)是 HTTP 缓存机制的一种,用于在资源过期后(强缓存失效时),通过向服务器发送验证请求,确认资源是否已被修改。若资源未修改,服务器返回 304 Not Modified,浏览器继续使用本地缓存;若资源已修改,则返回新资源。这种方式减少了不必要的网络传输,优化性能。


一、协商缓存的核心流程

  1. 浏览器发送请求
    当强缓存(如 Cache-Control: max-age)过期后,浏览器向服务器发起请求,携带验证字段(如 If-Modified-SinceIf-None-Match)。

  2. 服务器验证资源
    服务器根据请求头中的验证字段,判断资源是否修改:

    • 未修改 → 返回 304 Not Modified(不携带资源内容)。
    • 已修改 → 返回 200 OK 和新资源。
  3. 浏览器处理响应

    • 收到 304 → 复用本地缓存。
    • 收到 200 → 使用新资源并更新缓存。

二、协商缓存的验证字段

1. 基于修改时间:Last-ModifiedIf-Modified-Since

  • 响应头Last-Modified: <GMT 时间>
    服务器告知资源的最后修改时间(如 Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT)。
  • 请求头If-Modified-Since: <GMT 时间>
    浏览器发送资源上次的修改时间,询问是否变化。

局限性

  • 时间精度为秒级,若资源在 1 秒内多次修改可能无法检测。
  • 文件内容未变但修改时间更新(如重新生成文件)会导致误判。

2. 基于内容标识:ETagIf-None-Match

  • 响应头ETag: <唯一标识>
    服务器生成资源的唯一标识(如哈希值 ETag: "33a64df551425fcc55e4d42a148795d9")。
  • 请求头If-None-Match: <ETag 值>
    浏览器发送缓存的 ETag,询问是否匹配。

优势

  • 精确检测内容变化(即使修改时间未变)。
  • 支持弱验证(W/ 前缀,如 ETag: W/"0815"),允许内容语义不变时复用缓存。

三、协商缓存 vs 强缓存

对比维度强缓存协商缓存
触发条件缓存未过期缓存已过期
HTTP 状态码200 (from disk cache)304 Not Modified
网络请求无请求有请求(验证缓存有效性)
关键头部字段Cache-ControlExpiresLast-ModifiedETag
性能影响最佳(直接读取本地缓存)次优(需与服务器通信验证)

四、实际场景示例

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-ModifiedETag

  • 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

六、最佳实践

  1. 强缓存 + 协商缓存结合

    • 对静态资源(如 CSS、JS)设置较长 max-age(如 1 年),通过文件名哈希解决更新问题。
    • 资源过期后通过协商缓存验证。
  2. 优先使用 ETag
    避免 Last-Modified 因时间精度或文件元数据变化导致的误判。

  3. 避免过度协商
    对频繁变动的接口数据禁用缓存(Cache-Control: no-store)。


通过合理使用协商缓存,可以显著减少网络带宽消耗,提升用户体验,尤其适用于内容更新不频繁但需要长期缓存的场景。