HTTP 缓存完全指南:强缓存、协商缓存与 CDN 缓存策略全解析
Web/SEO赵婉清
HTTP 缓存:性能优化的第一道防线
合理配置缓存,可以让二次访问零网络请求,加载时间从秒级降到毫秒级。
| 缓存类型 | 机制 | 是否请求服务器 | 速度 |
|---|---|---|---|
| 强缓存 | Cache-Control / Expires | 否 | 最快(0ms) |
| 协商缓存 | ETag / Last-Modified | 是(条件请求) | 快(304 响应) |
| 无缓存 | no-cache / no-store | 是(完整请求) | 最慢 |
一、强缓存
Cache-Control 指令
Cache-Control: max-age=31536000, immutable
| 指令 | 作用 | 适用场景 |
|---|---|---|
max-age=N |
缓存 N 秒 | 静态资源 |
s-maxage=N |
CDN 缓存 N 秒(覆盖 max-age) | CDN 场景 |
immutable |
即使过期也不重新验证 | 带哈希的文件名 |
public |
允许中间缓存 | CDN |
private |
仅浏览器缓存 | 用户个性化内容 |
no-cache |
每次使用前必须验证 | HTML 页面 |
no-store |
完全不缓存 | 敏感数据 |
must-revalidate |
过期后必须验证 | API 响应 |
stale-while-revalidate=N |
过期后先返回旧值,后台刷新 | 渐进更新 |
Expires(旧标准)
Expires: Thu, 02 Jun 2027 00:00:00 GMT
优先级:Cache-Control: max-age > Expires。
二、协商缓存
ETag / If-None-Match
# 首次请求
GET /api/data
→ 200 OK
ETag: "abc123"
Cache-Control: max-age=0
# 再次请求(带条件)
GET /api/data
If-None-Match: "abc123"
→ 304 Not Modified (无响应体,节省带宽)
Last-Modified / If-Modified-Since
# 首次请求
GET /style.css
→ 200 OK
Last-Modified: Mon, 01 Jun 2026 12:00:00 GMT
# 再次请求
GET /style.css
If-Modified-Since: Mon, 01 Jun 2026 12:00:00 GMT
→ 304 Not Modified
ETag vs Last-Modified
| 维度 | ETag | Last-Modified |
|---|---|---|
| 精度 | 字节级 | 秒级 |
| 性能 | 需计算哈希 | 直接读文件时间 |
| 可靠性 | 高 | 低(1 秒内多次修改丢失) |
| 分布式 | 需保证哈希一致 | 天然一致 |
推荐:优先使用 ETag。
三、缓存策略矩阵
HTML 页面
Cache-Control: no-cache
HTML 是入口文件,必须每次验证,确保用户拿到最新版本。
带 Hash 的静态资源
Cache-Control: max-age=31536000, immutable
文件名含 Hash(如 app.abc123.js),内容变化时 Hash 变化 → URL 变化 → 新请求。旧 URL 永远不会变,可以缓存一年且标记 immutable。
不带 Hash 的静态资源
Cache-Control: max-age=86400, stale-while-revalidate=604800
过期后先返回旧值,后台静默刷新,用户无感知。
API 响应
Cache-Control: private, max-age=0, must-revalidate
API 响应通常含用户数据,private 禁止 CDN 缓存,must-revalidate 确保过期后不返回旧值。
四、Vary 头:缓存键的扩展
Vary: Accept-Encoding, Accept-Language
Vary 告诉缓存:除了 URL,还要根据这些请求头的值区分缓存版本。
| Vary 值 | 含义 | 场景 |
|---|---|---|
Accept-Encoding |
按压缩方式缓存 | gzip vs br |
Accept-Language |
按语言缓存 | 多语言站点 |
Cookie |
按 Cookie 缓存 | 个性化页面 |
* |
每个请求独立缓存 | 等于不缓存 |
注意:Vary: Cookie 会导致缓存命中率极低,慎用。
五、CDN 缓存策略
回源控制
# CDN 缓存 1 天,浏览器缓存 5 分钟
Cache-Control: public, s-maxage=86400, max-age=300
| 场景 | s-maxage | max-age | 说明 |
|---|---|---|---|
| 静态资源 | 31536000 | 31536000 | CDN 和浏览器都长缓存 |
| API 响应 | 300 | 0 | CDN 缓存 5 分钟,浏览器不缓存 |
| HTML | 60 | 0 | CDN 缓存 1 分钟,浏览器不缓存 |
| 个性化内容 | — | 0 | CDN 不缓存,浏览器不缓存 |
CDN 缓存刷新
| 方式 | 速度 | 适用 |
|---|---|---|
| URL 预热 | 快 | 新内容上线 |
| 全量刷新 | 慢 | 紧急修复 |
| 目录刷新 | 中 | 批量更新 |
| Cache-Control: no-cache | 自动 | API 场景 |
Stale-While-Revalidate 实战
Cache-Control: max-age=60, stale-while-revalidate=86400
第 0 秒:请求 → 缓存 MISS → 回源 → 返回新鲜值
第 30 秒:请求 → 缓存 HIT → 返回缓存值
第 90 秒:请求 → 缓存 STALE → 立即返回旧值 + 后台回源
第 91 秒:后台回源完成 → 缓存更新
第 120 秒:请求 → 缓存 HIT → 返回新值
效果:用户永远不等待回源,后台静默更新。
六、缓存破坏策略
内容哈希(推荐)
<!-- 构建时生成 Hash -->
<script src="/app.abc123.js"></script>
<link href="/style.def456.css">
// webpack 配置
output: {
filename: '[name].[contenthash:8].js',
}
版本号查询参数
<!-- 简单但 CDN 可能不缓存 ?v= 参数 -->
<script src="/app.js?v=2.0.0"></script>
问题:某些 CDN 忽略查询参数,导致缓存不更新。
时间戳(不推荐)
<!-- 每次都变,完全破坏缓存 -->
<script src="/app.js?t=1717315200"></script>
七、Service Worker 缓存
Cache First 策略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request))
);
});
Stale While Revalidate 策略
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('dynamic').then(cache =>
cache.match(event.request).then(cached => {
const fetchPromise = fetch(event.request)
.then(response => {
cache.put(event.request, response.clone());
return response;
});
return cached || fetchPromise;
})
)
);
});
缓存策略选择
| 策略 | 优先来源 | 适用 |
|---|---|---|
| Cache First | 缓存 | 字体、图片 |
| Network First | 网络 | API、HTML |
| Stale While Revalidate | 缓存+后台更新 | CSS、JS |
| Network Only | 网络 | 非 GET 请求 |
| Cache Only | 缓存 | 离线页面 |
八、调试缓存
Chrome DevTools
- DevTools → Network 面板
- 查看
Size列:from disk cache:磁盘缓存命中from memory cache:内存缓存命中- 实际大小:未命中
- 查看
Age响应头:缓存已存在的时间 - 勾选 Disable cache:禁用缓存(DevTools 打开时)
curl 验证
# 查看响应头
curl -I https://example.com/style.css
# 发送条件请求
curl -I -H 'If-None-Match: "abc123"' https://example.com/style.css
# 304 表示协商缓存命中
# 模拟不同 Accept-Encoding
curl -I -H 'Accept-Encoding: br' https://example.com/style.css
九、最佳实践清单
- HTML:no-cache,确保入口始终最新
- 带 Hash 的 JS/CSS:max-age=1年 + immutable
- API:must-revalidate,过期后必须验证
- CDN:s-maxage 控制回源频率
- Vary: Accept-Encoding,区分压缩方式
- 使用 ETag 而非 Last-Modified
- stale-while-revalidate 实现无感更新
- Service Worker 补充离线能力
#HTTP缓存#Cache-Control#ETag#CDN#性能优化