请谈谈 HTTP 中的缓存控制,如何使用 Cache-Control 和 ETag 进行缓存管理?
一、缓存控制的核心逻辑(用说人话的方式)
想象你每天早上去便利店买牛奶。缓存机制就像老板问你:"还是拿和昨天一样的盒装鲜奶吗?" 如果牛奶没过期(缓存有效),直接拿走不用付钱(不请求服务器);如果过期了(缓存失效),老板才会去仓库拿新的(重新请求)。
关键角色:
Cache-Control
:便利店老板的备忘录,写着"鲜奶保质期3天"ETag
:牛奶盒上的唯一编号,用来确认是不是同一批货Last-Modified
:牛奶的生产日期
二、缓存工作流程(配真实案例)
2.1 强缓存阶段(不询问服务器)
# 首次请求响应头
Cache-Control: max-age=3600 # 1小时有效期
ETag: "abc123"
此时浏览器直接把资源存到内存(memory cache)或磁盘(disk cache),1小时内重复请求直接读取缓存,不会产生任何网络请求(Chrome DevTools看到灰色from disk cache
)
2.2 协商缓存阶段(需要问服务器)
当缓存过期后,浏览器带着「身份证」问服务器:
# 二次请求头
If-None-Match: "abc123" # 上次的ETag值
服务器对比ETag:
- 相同 → 返回
304 Not Modified
(空body,省流量) - 不同 → 返回新资源+新ETag
三、缓存头字段详解(配代码实战)
3.1 Cache-Control常用指令
// Node.js设置响应头(Express示例)
app.get('/static/logo.png', (req, res) => {
// 强缓存1年(适用于带hash的静态资源)
res.set('Cache-Control', 'public, max-age=31536000, immutable')
// 禁止缓存(敏感数据)
// res.set('Cache-Control', 'no-store')
// 缓存但每次验证(适合频繁更新的API)
// res.set('Cache-Control', 'no-cache')
})
指令对比表:
指令 | 效果 | 适用场景 |
---|---|---|
max-age=3600 | 缓存有效期3600秒 | 静态资源 |
no-cache | 缓存但每次必须验证 | 需要实时性的数据 |
no-store | 禁止存储任何缓存 | 敏感数据(如支付接口) |
immutable | 即使过期也直接使用 | 带hash的静态文件 |
3.2 ETag生成策略
// 用文件内容生成ETag(避免时间戳导致的误判)
const crypto = require('crypto')
const fileContent = fs.readFileSync('data.json')
const etag = crypto.createHash('md5').update(fileContent).digest('hex')
app.get('/api/data', (req, res) => {
res.set('ETag', etag)
// 处理协商缓存
if (req.headers['if-none-match'] === etag) {
return res.status(304).end()
}
res.json({ data: 'new content' })
})
四、开发中的缓存管理技巧(避坑指南)
4.1 静态资源最佳实践
<!-- 带hash的文件名实现永久缓存 -->
<script src="/js/app.3a87b6.js"></script>
<!-- 后端配置 -->
app.use(express.static('public', {
maxAge: '1y',
immutable: true // 关键!告诉浏览器不用验证
}))
4.2 动态接口防缓存污染
// 强制跳过浏览器缓存(调试用)
fetch('/api/user', {
headers: {
'Cache-Control': 'no-cache', // 添加该头让浏览器强制验证
'Pragma': 'no-cache' // 兼容旧浏览器
}
})
4.3 CDN缓存联动
# 源服务器响应头
Cache-Control: public, max-age=600 # 告诉CDN缓存10分钟
CDN-Cache-Control: public, max-age=3600 # CDN覆盖设置(部分厂商支持)
五、实际踩坑案例分析
5.1 版本号失效问题
<!-- 错误示范:用时间戳导致缓存失效 -->
<link href="/css/style.css?v=20230815">
<!-- 正确做法:基于文件内容生成hash -->
<link href="/css/style.a3c8f1.css">
5.2 移动端缓存异常
iOS Safari有时会无视no-store
:
// 添加随机参数绕过缓存
fetch(`/api/items?t=${Date.now()}`)
5.3 浏览器前进后退陷阱
某些浏览器在「前进/后退」时直接使用内存缓存:
// 页面初始化时主动验证
window.addEventListener('pageshow', (event) => {
if (event.persisted) { // 从缓存恢复时触发
location.reload()
}
})
六、缓存调试技巧(Chrome DevTools实战)
-
Network面板过滤:
- 灰色标记
(from disk cache)
→ 强缓存生效 - 304状态码 → 协商缓存生效
- 灰色标记
-
禁用缓存调试:
- 勾选DevTools的
Disable cache
(等效于Cache-Control: no-store
)
- 勾选DevTools的
-
强制刷新:
Ctrl+Shift+R
(Windows)清空缓存重新请求
总结 Checklist
- 静态资源:
Cache-Control: max-age=31536000, immutable
+ 带hash文件名 - 动态接口:
Cache-Control: no-cache
+ 合理设置ETag - 敏感数据:始终使用
Cache-Control: no-store
- 部署更新:修改文件名或添加版本号触发缓存失效
- 监控异常:通过日志监控304比例,优化缓存策略
缓存管理就像给网站做「物资调度」,合理使用能让加载速度提升数倍。
建议通过实际项目的瀑布流分析,观察哪些资源可以延长缓存时间,哪些需要严格控制实时性。记住:缓存不是洪水猛兽,用对了就是性能利器。