Cache-Control — 这是什么,指令和缓存管理

作者: IT Sectr 发布日期: 2026-03-09 阅读时间: 9 分钟

Cache-Control — 是一种 HTTP 头,通过一组指令确定客户端、代理服务器和 CDN 上资源的缓存规则。与已过时的 Expires 头不同,Cache-Control 支持数十种组合:max-age 以秒为单位设置存活时间,private 和 public 管理缓存的可用性,no-cache 和 no-store 进行强制检查。据 Google Web Dev (2025),正确配置 Cache-Control 可以将重复访问时的页面加载时间减少 50-80%。这使得该头对于网络和移动应用的性能至关重要。

要点

  • Cache-Control — 通过指令管理客户端、代理和 CDN 上缓存的 HTTP 头
  • max-age — 关键指令,以秒为单位设置资源的存活时间,无需重复检查
  • private vs public — private 仅允许在客户端缓存,public 也允许在代理和 CDN 上缓存
  • no-cache vs no-store — no-cache 要求在使用前检查,no-store 完全禁止缓存
  • s-maxage — 覆盖共享缓存的 max-age,不影响浏览器

什么是 Cache-Control?

Cache-Control — 是一种 HTTP 头,在 HTTP/1.1 (RFC 7234) 中标准化,允许服务器指定客户端、代理和 CDN 如何以及多长时间可以缓存响应。与 Expires (HTTP/1.0) 不同,Cache-Control 使用指令 — 通过逗号组合的文本命令:Cache-Control: public, max-age=3600, must-revalidate。该头对缓存链中的每一个环节提供精确控制。

缓存是网络和移动应用性能的基本机制之一。没有它,每个用户请求都会直接发送到服务器,导致过度负荷和延迟。Cache-Control 定义了三个缓存层级:浏览器/应用(private cache)、代理服务器(shared cache)和 CDN(distributed cache)。每一层都以自己的方式解释指令。

Cache-Control 的错误配置是性能问题最常见的原因之一。过于激进的缓存导致用户看到过时数据。过于弱的缓存导致对服务器的过多请求和加载缓慢。据 Akamai (2025),优化静态内容的 Cache-Control 可将服务器负荷减少 70-90%,并将移动用户的加载时间提高 40-60%。

头的历史

Cache-Control 出现在 HTTP/1.1 (RFC 2616, 1999)中,作为 Expires 的替代品。Expires 有一个根本问题:它使用绝对日期,该日期取决于服务器和客户端的时区。Cache-Control 通过转变为相对时间解决了这个问题(从接收响应的瞬间开始以秒为单位的 max-age)。随后,在 RFC 7234 (2014) 中添加了新指令:immutable 用于静态文件,stale-while-revalidate 和 stale-if-error 用于延迟检查。

Cache-Control 指令

Cache-Control 包含超过 10 个指令,分为三组:请求指令(客户端 → 服务器)、响应指令(服务器 → 客户端)和扩展。在实践中,移动开发中使用 6-7 个基本响应指令,覆盖 95% 的缓存场景。让我们通过示例和建议来查看每一个指令。

指令含义示例
max-age从响应瞬间开始以秒为单位的存活时间max-age=3600 — 1小时
s-maxage共享缓存的 max-age(代理、CDN)s-maxage=86400 — CDN 1天
public允许所有人缓存(包括代理)public, max-age=3600
private仅允许浏览器/应用缓存private, max-age=600
no-cache未经检查不得使用(304 必须)no-cache
no-store完全禁止缓存no-store
must-revalidatemax-age 后必须向源服务器验证max-age=3600, must-revalidate
immutable资源不会改变(用于版本化静态文件)max-age=31536000, immutable

max-age — 最重要的指令。禁止客户端在指定时间内向服务器发送请求。对于静态文件(CSS、JS、图片),max-age 通常设置为从 1 天到 1 年。对于 API 响应 — 从 0 秒(始终新鲜数据)到 5-10 分钟(参考数据)。s-maxage 允许为 CDN 和浏览器设置不同的存活时间:CDN 保存副本 1 天,浏览器 1 小时。

no-cache 与 no-store 的区别

这两个指令经常被混淆。no-cache 并不禁止缓存 — 它要求在每次使用时通过条件请求(If-Modified-Since 或 If-None-Match)检查缓存副本。如果服务器响应 304,则客户端使用缓存。如果 200,则更新。no-store 则完全禁止将响应保存在任何缓存中,包括磁盘和内存。只对敏感数据使用 no-store — 令牌、支付数据、个人文档。

Cache-Control 与 Expires 的区别

Expires 头(HTTP/1.0)也指示资源的存活时间,但使用绝对日期:Expires: Thu, 03 Jul 2026 12:00:00 GMT。Cache-Control max-age — 从响应瞬间开始的相对时间。这个差异对分布式系统至关重要:如果服务器和客户端所处时区不同,Expires 可能被错误解释。Cache-Control 没有这个问题 — 3600 秒始终是 3600 秒。

当两个头都存在时,Cache-Control 优先级高于 Expires。这在 RFC 7234 中规定:“如果响应包含带有 max-age 指令的 Cache-Control,则接收方必须忽略 Expires。”实际上,建议不要为现代客户端返回 Expires,因为 Cache-Control 覆盖了 Expires 的所有场景。但是,为了与旧代理和浏览器向后兼容,可以返回两个头。

Expires 主要保留在 Nginx 和 Apache 上的静态内容 — 这些服务器自动添加两个头。如果在项目中遇到没有 Cache-Control 的 Expires,请将其替换为带有 max-age 的 Cache-Control:缓存管理精度提高,对时区的依赖被消除。迁移时,只需配置服务器添加 Cache-Control 代替 Expires 即可。

nginx
# Nginx:静态文件的 Cache-Control
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 30d;
    add_header Cache-Control "public, immutable, max-age=2592000";
}

# 不同内容类型的不同策略
location /api/config {
    expires -1;
    add_header Cache-Control "no-cache, must-revalidate";
}

location /api/static-data {
    expires 5m;
    add_header Cache-Control "public, max-age=300";
}

在 Nginx 配置中,对于静态文件(CSS、JS、图片),Cache-Control 设置为 30 天带有 immutable 属性 — 该属性告诉浏览器资源在此 URL 下从不改变(通过文件名中的哈希进行版本控制)。API 端点对动态数据使用no-cache,对参考数据使用短 max-age 的 public — 经常请求且少有变化的列表。

移动应用中的缓存

在移动应用中,由于移动网络的限制(高延迟、不稳定连接、流量限制),Cache-Control 发挥着特殊作用。正确的缓存允许用户即时查看数据,甚至在离线情况下也能在后台更新它们。Android 上的 OkHttp 和 iOS 上的 URLSession 都有内置的缓存系统,能够考虑 Cache-Control。

OkHttp 使用 CacheInterceptor,它从响应中读取 Cache-Control 并自动管理缓存。如果服务器返回了 Cache-Control: max-age=3600,OkHttp 将在一小时内不向服务器发送请求。max-age 过期后,OkHttp 会发送含有 If-Modified-Since 和 If-None-Match 的条件请求。在 OkHttp 中配置缓存:OkHttpClient.Builder().cache(Cache(directory, maxSize))

kotlin
fun createCachedClient(cacheDir: File): OkHttpClient {
    return OkHttpClient.Builder()
        .cache(Cache(cacheDir, 10L * 1024 * 1024))
        .addNetworkInterceptor { chain ->
            val response = chain.proceed(chain.request())
            response.newBuilder()
                .header("Cache-Control",
                    "public, max-age=300")
                .removeHeader("Pragma")
                .build()
        }
        .build()
}

代码创建了一个带有 10 MB 缓存的 OkHttpClient,并通过 NetworkInterceptor 覆盖 Cache-Control。如果服务器不返回 Cache-Control 或使用 Expires,拦截器会添加public, max-age=300(5分钟)。拦截器会删除已过时的 Pragma 头(HTTP/1.0)以兼容。iOS 上的缓存也按相同方式工作,通过 URLCache.shared 配置 memoryCapacity 和 diskCapacity。

离线模式和 stale-while-revalidate

stale-while-revalidate 指令允许用户在应用后台加载新数据时查看过时缓存。这创造了即时响应的效果:用户立即看到内容,一秒钟后内容更新。OkHttp 从 3.10 版本开始支持,iOS 14+ 的 URLCache 也支持。示例:Cache-Control: max-age=3600, stale-while-revalidate=300 — 1小时当前缓存,然后 5分钟显示过时缓存并后台更新。

Cache-Control 配置示例

不同类型的资源需要不同的缓存策略。让我们查看移动开发中典型场景的最佳配置。对于文件名中含有哈希的静态内容(如 bundle.abc123.js),可以将 max-age 设置为达到 1 年并且 immutable。对于少有更新的 API 列表(目录、分类),max-age 从 5 分钟到 1 小时,含有 stale-while-revalidate。

资源类型Cache-Control说明
版本化静态文件public, max-age=31536000, immutable1年,文件不变(URL 中的哈希)
非版本化静态文件public, max-age=86400, must-revalidate1天,之后必须验证
API:参考数据public, max-age=600, stale-while-revalidate=6010分钟缓存 + 1分钟 stale
API:用户数据private, max-age=601分钟,仅限特定用户
API:敏感数据no-store完全禁止缓存
HTML 页面no-cache, must-revalidate每次请求检查,未变则 304

重要的是要记住安全:对于包含用户个人数据的响应,始终设置为 private。没有这个指令,公共代理(如企业代理)可以缓存响应并将其传递给另一个用户。对于身份验证令牌和支付信息,使用 no-store — 即使 private 缓存也不应将这些数据保存到磁盘。

调试缓存

要检查 Cache-Control 的正确性,请使用 Age 头(缓存已保存了多少秒)和 X-Cache(CDN 上的 hit/miss)。在浏览器中 — Network 选项卡,Size 列显示“from disk cache”或“304 Not Modified”。如果资源应该被缓存但每次都加载,请检查服务器是否在您的指令之外添加了Cache-Control: no-cachePragma: no-cache

常见问题

max-age 和 s-maxage 之间的区别是什么?

max-age 适用于所有缓存(包括浏览器),s-maxage 仅适用于共享缓存(代理、CDN)。如果指定了 s-maxage,CDN会忽略 max-age 并使用 s-maxage。这允许为浏览器和 CDN 设置不同的存活时间。

发送 Cache-Control 后可以取消缓存吗?

不可以。发送含有 max-age 的响应后,客户端将不会发送请求,直到定时器到期。要立即使缓存失效,需要修改资源的 URL(添加版本/哈希)并发送推送通知或WebSocket 消息以强制重置。

什么是 immutable 指令?

immutable 指令(RFC 8246)告诉浏览器资源在此 URL 下永远不会改变。浏览器在刷新页面时甚至不会尝试发送条件请求 — 直到 max-age 过期前一直使用缓存。仅在版本化文件上有效。

Cache-Control 如何影响 SEO?

Googlebot 会考虑 Cache-Control:长时间缓存会加快重新扫描。带快速缓存的 noindex — OK。no-store 可能会减慢索引,因为Googlebot将每次从头加载页面。太短的 max-age 会增加扫描时的服务器负荷。

如何在 Express.js 中配置 Cache-Control?

通过 helmet 或中间件:res.set('Cache-Control', 'public, max-age=3600')。对于静态文件,使用带有 maxAge 参数的 express.static:express.static('public', {maxAge: '1y'})。对于动态路由,在每个处理程序中单独配置。

总结

  • Cache-Control — 具有灵活指令系统的主要 HTTP 缓存管理头
  • max-age — 从响应瞬间开始以秒为单位的存活时间;所有缓存场景的关键指令
  • private vs public — private 仅用于客户端,public 用于代理和 CDN;影响数据安全
  • no-cache 需要检查,no-store 完全禁止缓存;目的不同,不要混淆
  • s-maxage — 覆盖共享缓存的 max-age,有助于区分浏览器/CDN 策略
  • stale-while-revalidate — 显示过时缓存并后台更新,实现即时 UX
  • 建议 — 为服务器和移动 HTTP 客户端上的每种资源类型配置 Cache-Control

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读