上一篇讲强缓存,它在有效期内直接用本地副本,连请求都不发。但有效期一过,强缓存就失效了。这时候问题来了:有效期过了,不代表资源真的变了——可能内容一点没变,只是 max-age 到期了。
如果这时候重新下载整个资源,太浪费。协商缓存就是为这个场景设计的:不发整个资源,只问服务器一句「我手上的副本还能用吗」,能用就返回 304,省掉传输 body 的开销。
协商缓存:发请求,但只问「变了没」
协商缓存和强缓存的区别在于发不发请求:
- 强缓存:不发请求,直接用本地副本。
- 协商缓存:发一个请求,但带上「我这个副本的标识」,让服务器判断。如果没变,服务器返回
304 Not Modified,不返回 body,浏览器继续用本地副本;如果变了,返回200和新资源。
304 的价值在于省掉了 body 传输。如果资源是 2MB 的图片,304 响应只有几百字节的 header,省了 2MB 带宽和传输时间。这就是为什么协商缓存比「无缓存」快得多,虽然不如强缓存(强缓存连请求都省)。
两套标识:ETag 和 Last-Modified
协商缓存靠「资源标识」来判断内容变没变。HTTP 提供了两套标识:
第一套:Last-Modified(基于时间)
- 服务器响应里带
Last-Modified: Wed, 27 Jun 2026 10:00:00 GMT(资源最后修改时间)。 - 下次请求,浏览器带上
If-Modified-Since: Wed, 27 Jun 2026 10:00:00 GMT。 - 服务器比较:如果资源在这个时间后没改过,返回 304;改过,返回 200 + 新资源。
这套机制简单,但有个精度问题:它只精确到秒。如果资源在 1 秒内改了两次,Last-Modified 看不出区别。而且有些资源内容没变但修改时间变了(比如重新部署但内容相同),Last-Modified 会误判为「变了」。
第二套:ETag(基于内容哈希)
- 服务器响应里带
ETag: "abc123"(资源内容的哈希或版本标识)。 - 下次请求,浏览器带上
If-None-Match: "abc123"。 - 服务器比较:如果内容哈希相同,返回 304;不同,返回 200 + 新资源。
ETag 解决了 Last-Modified 的精度问题——它基于内容,只要内容相同 ETag 就相同,不管修改时间。而且 ETag 精度无限(哈希可以精确到字节级别)。
两套同时存在:ETag 优先
ETag 和 Last-Modified 可以同时存在。当请求同时带 If-None-Match(ETag)和 If-Modified-Since(Last-Modified)时,服务器优先验证 ETag。只有 ETag 验证通过,才会验证 Last-Modified。
为什么 ETag 优先?因为 ETag 更精确。Last-Modified 只到秒,且受「改时间没改内容」干扰,ETag 基于 content hash 更可靠。服务器如果只支持 Last-Modified(没生成 ETag),才退化到用 Last-Modified。
协商缓存的真实流程
把两套机制串起来,一次完整的协商缓存流程是这样的:
- 首次请求:服务器返回资源,响应头带
ETag: "abc123"和Last-Modified: ...。浏览器把资源和这两个标识一起缓存。 - 强缓存过期后再次请求:浏览器带上
If-None-Match: "abc123"和If-Modified-Since: ...。 - 服务器验证:检查 ETag 和 Last-Modified。
- 没变:返回
304 Not Modified,不带 body。浏览器用本地副本,并刷新过期时间。 - 变了:返回
200 OK+ 新资源 + 新的 ETag/Last-Modified。浏览器用新资源,更新缓存。
取舍与边界
协商缓存有几个值得注意的点:
- ETag 的计算有成本。服务器要为每个资源算哈希(或版本号)。对于静态文件,可以基于文件内容算;对于动态内容(API 响应),每次都要算,可能有性能开销。有些场景退而用 Last-Modified 就是为了省这个成本。
- 弱 ETag(Weak ETag)。ETag 有强弱之分:强 ETag(
"abc123")要求字节级完全一致;弱 ETag(W/"abc123")允许语义相同但字节不同(比如注释空格变了)。弱 ETag 适合「内容语义相同即可」的场景。 - 协商缓存仍要发请求。它不如强缓存(零请求),所以最佳实践是:先靠强缓存扛住大部分请求,强缓存失效后靠协商缓存兜底。两者是接力关系,不是替代关系。
收束:强缓存是第一道闸,协商缓存是第二道
HTTP 缓存是两道关卡的接力:强缓存(不发请求)失效后,协商缓存(发请求但不传 body)接上。强缓存扛住有效期内的高频访问,协商缓存兜住过期后的访问,把「重新下载整个资源」降到最小。
下一篇讲 Vary——同一个 URL,为什么不同客户端可能拿到不同的响应?缓存怎么区分这些「同一个 URL 但内容不同」的情况?
关于十三Tech
我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。
我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。
如果你想继续跟完这套「图解 HTTP」,欢迎关注公众号 「十三Tech」。后续会按 URL 与报文、连接与传输、缓存与协商、安全与边界、HTTP/2 与 HTTP/3 这条线更新。

