Cache-Control 是什么
Cache-Control 是 HTTP 缓存的最高优先级指令集:请求头用它声明客户端能接受什么,响应头用它规定浏览器与共享缓存可以存多久、要不要回源再验证。它比更早的过期时间头优先级更高,也是现代缓存策略的唯一可靠入口。理解它的关键是分清两个概念——新鲜度(在多久之内可以直接用本地副本)与再验证(过期后必须回源确认是否变化)。
基本信息
| 名称 | Cache-Control(RFC 9111 §5.2) |
|---|---|
| 作用 | 定义请求与响应的缓存策略 |
| 优先级 | 高于过期时间头,冲突时以它为准 |
| 常用指令 | max-age、s-maxage、no-store、no-cache、private、immutable |
| 作用对象 | 浏览器私有缓存与 CDN 等共享缓存,两者可用指令区分 |
响应侧高频指令
| 指令 | 含义 |
|---|---|
| max-age=600 | 响应在 600 秒内是新鲜的,可直接使用本地副本 |
| s-maxage=3600 | 只约束共享缓存的时长,优先级高于 max-age |
| no-store | 任何缓存都不得存储,适合含敏感数据的响应 |
| no-cache | 可以存储,但每次使用前必须回源再验证 |
| private | 只允许浏览器缓存,共享缓存不得存储 |
| immutable | 声明内容不会变,浏览器跳过再验证直接使用 |
| must-revalidate | 过期后必须回源验证,不允许继续用陈旧副本 |
典型使用场景
- 带指纹的静态资源:设置一年期长缓存并声明内容不变,发布靠改文件名失效
- 入口页面:不设长缓存而要求每次再验证,保证用户能及时拿到新版本
- 含用户隐私的接口响应:声明任何缓存都不得存储,避免数据落在中间节点
- CDN 加速:用共享缓存指令给边缘节点更长或更短的时长
- 接口限流与降级:配合过期时间让上游压力更平滑
静态资源长缓存配置
静态资源长缓存配置
location /static/ {
# 文件名带哈希,内容变了文件名也变,因此可以放心长期缓存
add_header Cache-Control "public, max-age=31536000, immutable";
}
location = /index.html {
add_header Cache-Control "no-cache"; # 入口页面每次回源验证,保证能发新版
}核对实际生效的缓存头
核对实际生效的缓存头
curl -sI https://www.example.com/static/app.9f2c1a.js | grep -i cache-control # 看静态资源
curl -sI https://www.example.com/ | grep -iE 'cache-control|etag|age' # 看入口页与校验值
curl -sI https://www.example.com/static/app.9f2c1a.js | grep -i age # 共享缓存命中时会出现年龄,表示已在边缘节点停留多久两个最常被写错的指令:不缓存要用不存储指令,可存储但每次验证用不验证缓存指令。很多人把后者当成「不要缓存」,结果把敏感响应留在了中间缓存里。
浏览器缓存与 CDN 缓存要分开理解:共享缓存指令只作用于 CDN 一类共享节点,改错作用域会出现「服务器配置明明改了,用户还是拿到旧版本」的困惑。
四种常见组合
- 带指纹的静态资源:公开可缓存加一年期时长并声明内容不变,发布靠改文件名失效
- 入口页面:声明每次回源验证,既保证用户及时拿到新版本又不浪费带宽
- 变化不频繁的只读接口:给一个分钟级的新鲜期,兼顾实时性与上游压力
- 含个人隐私的响应:声明任何缓存都不得存储,并配套使用加密访问
判断缓存到底有没有命中
| 观察点 | 含义 |
|---|---|
| 响应里的年龄字段 | 记录内容在共享节点已经停留了多久,命中时明显大于零 |
| 浏览器开发者工具的尺寸列 | 显示来自磁盘或内存缓存,说明强缓存命中 |
| 状态码是否未修改 | 只交换头部即命中协商缓存,省下响应体 |
缓存策略改动后要用真实入口验证:命令行看头部、浏览器看命中列、CDN 看回源日志,三层都确认才算真的生效,只改一处往往被另一层覆盖。