HTTP 缓存策略:强缓存、协商缓存与 CDN

FreeGuideOnline 最新 2026-06-15

HTTP 缓存策略核心概念

HTTP 缓存是 Web 性能优化中最关键的技术之一。它允许浏览器或中间代理服务器存储资源的副本,在后续请求时直接使用副本而无需从源服务器重新下载。这不仅大幅降低了服务器负载,还显著减少了页面加载时间,改善了用户体验。

从策略层面,HTTP 缓存主要分为两大类:强缓存协商缓存。理解它们的区别与协作方式,是掌握缓存优化的基础。

强缓存:无需询问服务器

强缓存是指浏览器直接使用本地缓存的资源,不会向服务器发送任何请求。只要缓存未过期,资源就从本地读取,状态码表现为 200 (from memory cache)200 (from disk cache)

控制强缓存的核心 HTTP 响应头有两种:

  • Expires:HTTP/1.0 时代的产物,值为一个绝对时间的 GMT 格式字符串。例如 Expires: Thu, 01 Dec 2025 16:00:00 GMT,告诉浏览器在这个时间点之前可以直接使用缓存。其缺点明显:依赖客户端本地时间,如果客户端时间不准确,缓存判断就会出错。

  • Cache-Control:HTTP/1.1 引入的现代化缓存控制头,优先级高于 Expires。它使用相对时间或指令,功能更强大、更灵活。常用指令包括:

    • max-age=<seconds>:资源被视为新鲜的最大时长,单位为秒。例如 Cache-Control: max-age=31536000 表示资源在一年内都有效。这是最常用的强缓存指令。
    • s-maxage=<seconds>:仅用于共享缓存(如 CDN),覆盖 max-age 对共享缓存的作用。
    • public:表明响应可以被任何对象(浏览器、CDN 等)缓存。
    • private:表明响应只能被浏览器缓存,不允许中间代理缓存。
    • no-cache不是不缓存,而是要求在使用缓存前必须向服务器进行新鲜度验证(即必须走协商缓存)。
    • no-store:真正禁止缓存,每次都必须从服务器获取完整响应。
    • must-revalidate:缓存过期后,必须重新验证才能使用,不能直接使用过期的缓存。

强缓存验证流程

  1. 浏览器首次请求资源,服务器返回 Cache-Control: max-age=3600
  2. 一小时内再次请求该资源,浏览器直接检查缓存,未过期则直接从缓存中取出资源,网络面板显示 200 (disk cache)
  3. 一小时后请求,缓存过期,进入协商缓存阶段。

协商缓存:服务器验证新鲜度

当强缓存失效,或用户明确刷新页面(如按 F5),浏览器会携带缓存标识向服务器发送条件请求。服务器根据标识判断资源是否变化:

  • 如果未变化,返回 304 Not Modified,浏览器继续使用本地缓存,此时只传输了很短的响应头,无响应体,节省带宽。
  • 如果已变化,返回 200 及新的资源内容和新的缓存头。

协商缓存依赖两对请求/响应头:

基于最后修改时间:Last-Modified / If-Modified-Since

  • 服务器在首次响应中返回 Last-Modified 头,表示资源的最后修改时间。
  • 浏览器缓存此时间,下次请求时通过 If-Modified-Since 头将该时间发送给服务器。
  • 服务器对比资源的最后修改时间:
    • 如果一致,返回 304,浏览器使用缓存。
    • 如果不一致,返回 200 并带上新的资源和新的 Last-Modified

这种方式有两个缺陷:

  • 时间精度通常为秒级,如果资源在 1 秒内多次修改,缓存可能无法及时更新。
  • 某些场景下文件被定期生成,内容实际未变但 Last-Modified 变了,导致无效的重新下载。

基于内容标识:ETag / If-None-Match

ETag(Entity Tag)是资源的唯一标识,通常是文件内容的哈希值或版本号。它解决了 Last-Modified 的精度问题,是更可靠的验证方式。

  • 服务器返回 ETag: "abc123"
  • 浏览器保存该值,下次请求通过 If-None-Match: "abc123" 发送给服务器。
  • 服务器比对 ETag
    • 相同则返回 304
    • 不同则返回 200 及新内容与新的 ETag

ETag 优先级高于 Last-Modified。当两者同时存在时,服务器会先检查 If-None-Match,验证通过后不会再去检查 If-Modified-Since

协商缓存请求流程示意(强制刷新或 max-age 过期后):

  1. 浏览器请求资源,自动携带 If-None-Match 和/或 If-Modified-Since
  2. 服务器根据 ETagLast-Modified 判断资源未变,返回 304 Not Modified
  3. 浏览器收到 304,从缓存中读取资源并更新缓存有效期(根据返回的新鲜缓存头)。

强缓存与协商缓存的协作

一个完整的缓存决策流程如下:

  1. 浏览器发起请求
  2. 检查强缓存:如果 Cache-Control: max-age 未过期或 Expires 时间未到,则直接使用缓存,不发送请求。
  3. 强缓存失效:发送请求,自动带上协商缓存的验证条件(If-None-MatchIf-Modified-Since)。
  4. 服务器验证
    • 资源未变 → 返回 304,浏览器继续使用缓存。
    • 资源已变 → 返回 200 和新资源,更新本地缓存和缓存头。

在实际项目中,常使用 Cache-Control: max-age=0, must-revalidateCache-Control: no-cache 来直接进入协商缓存,这常用于 HTML 主文档的缓存策略——每次请求都验证,但未修改时只返回 304,既保证实时性又节省带宽。

CDN 与 HTTP 缓存

CDN(内容分发网络)是广泛部署在全球各地的缓存服务器网络。它将源站内容分发到距离用户更近的边缘节点,通过 HTTP 缓存头控制 CDN 节点的缓存行为。

CDN 缓存的工作方式

CDN 节点本质上是一个巨大的“共享缓存”。当用户请求某个资源时:

  • 如果 CDN 节点已有该资源的有效缓存,则直接返回给用户,无需回源。
  • 如果缓存过期或不存在,CDN 向源站发起请求,获取资源后存储在节点上,再返回给用户。

CDN 缓存同样遵循 HTTP 缓存头,但有一些特殊之处:

  • s-maxage 可以单独控制 CDN 的缓存时长,而不影响浏览器缓存。
  • public 明确告知 CDN 该资源可缓存,private 则告知不可缓存。
  • no-cache 在 CDN 中的行为可能被重写,有些 CDN 提供配置项将 no-cache 作为需要验证的指令遵守,而有些则会直接理解为不缓存。

CDN 缓存层级与刷新

CDN 通常有层级缓存架构,例如 L1(边缘节点)和 L2(父节点)。大多数请求会被边缘节点直接响应,边缘节点未命中时逐级向上请求,最终回源。这种机制大大减轻了源站压力。

CDN 缓存刷新是运维的重要操作:

  • 目录刷新:更新某个目录下所有资源的缓存(例如网站改版后刷新 /static/ 下所有文件)。
  • URL 刷新:使单个资源的缓存立即失效。
  • 预热:主动将静态资源推送到 CDN 节点,这样用户首次访问时就能命中缓存。

结合强缓存与 CDN 的最佳实践

对于不常变化的静态资源(CSS、JS、图片、字体),推荐采用文件名指纹(hash)长期强缓存结合 CDN 的策略:

  • 构建工具为每个文件生成唯一的哈希值,如 main.1a2b3c.js
  • 服务器设置极大的 Cache-Control: max-age=31536000, public(一年),并配置 CDN 也遵守该头。
  • 当文件内容更新时,文件名随之改变,相当于发布了一个新 URL。旧的 URL 仍然被缓存,新 URL 强制获取新内容,这样既能享受极致的缓存,又不存在更新不及时的问题。

对于 HTML 主文档,通常设置 Cache-Control: no-cache 并让 CDN 回源验证,保证用户总能获取最新页面结构。

缓存策略速查表

场景 Cache-Control 头示例 说明
长期不变静态资源(带指纹) public, max-age=31536000, immutable 一年强缓存,immutable 表示资源永不变,浏览器无需重验证
可能变化的静态资源 max-age=604800 一周强缓存,过期后协商缓存
HTML 文档 no-cachemax-age=0, must-revalidate 每次验证,保证实时性,但仍可借助304节省传输
敏感数据(用户私有) private, no-cache, no-store 仅浏览器缓存,每次验证,部分场景需完全禁止存储
CDN 缓存但浏览器不缓存 s-maxage=3600, max-age=0 CDN 缓存一小时,浏览器每次需验证

调试与验证缓存行为

实际开发中,可通过以下方式确认缓存是否生效:

  • 浏览器开发者工具 (DevTools)
    • Network 面板的 Size 列显示 (disk cache)(memory cache) 表示来自强缓存。
    • 状态码 304 表示协商缓存命中。
    • 查看请求的 Request Headers 确认是否携带 If-None-Match 等。
  • cURL 命令
    curl -I https://example.com/style.css
    
    返回的响应头包含 Cache-ControlETagLast-Modified 等信息。
  • CDN 提供商的缓存命中率面板:查看命中和回源次数,判断配置是否合理。

常见误区与注意事项

  1. 认为 no-cache 是不缓存no-cache 意味着每次使用前必须验证,资源本身仍可被缓存;真正禁止缓存的是 no-store
  2. 同时设置强缓存和 no-cache 导致混淆Cache-Control: max-age=3600, no-cache 虽然不常见,但规范中 no-cache 会覆盖 max-age,导致每次都验证。应避免无意义组合。
  3. 只依赖 ETag 而忽略 Last-Modified:在某些 CDN 或旧系统中,ETag 可能被剥离或计算方式导致不一致,保留 Last-Modified 作为兜底是可靠的选择。
  4. 刷新按钮与强缓存的关系:普通刷新 (F5) 会忽略强缓存直接带条件请求;硬刷新 (Ctrl+F5) 则会加上 Cache-Control: no-cachePragma: no-cache 请求头,强制跳过所有缓存。
  5. 缓存静态资源但忘记更新文件名:这是性能优化的经典陷阱,导致用户浏览器长时间使用旧版本文件,必须配合文件指纹策略。

总结

合理的 HTTP 缓存策略是构建高性能 Web 应用的基石。通过结合强缓存与协商缓存,并利用 CDN 的分布式缓存能力,可以极大提升资源加载速度。核心原则是:为不变的资源分配唯一的 URL 和长久的缓存时间,为变化的资源配置即时验证机制。 掌握这套方法论,你就能在绝大多数场景下设计出高效、可靠的缓存方案。