深入理解 HTTP 缓存机制:从原理到实践优化
一、HTTP 缓存的核心价值与基本分类
二、强缓存:本地资源的 “免验证通道”
1. 强缓存的两种实现方式
(1)Expires:HTTP/1.0 的时间戳标记
Expires: Wed, 21 Oct 2027 07:28:00 GMT (2)cache-control:HTTP/1.1 的相对时间控制
cache-control: public, max-age=31536000 // 缓存1年 - public表示资源可被客户端和代理服务器共享缓存;
- max-age定义从首次请求开始计算的缓存时长,避免了 Expires 依赖客户端时间的问题。
2. 在服务端设置 cache-control
const express = require('express');
const app = express();
app.get('*', (req, res) => {
// 设置缓存10分钟(600秒),公共缓存
res.setHeader('cache-control', 'public, max-age=600');
res.end('资源内容');
});
app.listen(3031);
3. cache-control 的高级指令解析
| 指令分类 | 常用值 | 作用描述 |
|---|---|---|
| 可缓存性 | public | 资源可被任何设备缓存(包括代理服务器) |
| private | 资源仅可被用户本地浏览器缓存,代理服务器不可缓存 | |
| no-cache | 强制缓存前需先向服务器验证资源有效性(走协商缓存流程) | |
| no-store | 禁止任何形式的缓存,每次请求都重新获取资源 | |
| 到期时间 | max-age=600 | 资源在 600 秒内有效 |
| s-maxage=3600 | 共享缓存(如代理服务器)的过期时间,优先级高于 max-age | |
| 重新验证策略 | must-revalidate | 缓存过期后必须向服务器验证,否则不可使用 |
| immutable(实验性) | 资源内容永久不变,无需验证(仅 HTTPS 场景有效) |
4. 内存缓存与磁盘缓存的差异
- 内存缓存(Memory Cache):读取速度极快,但随浏览器标签页关闭而释放,主要存储当前页面资源(如脚本、样式)。
- 磁盘缓存(Disk Cache):容量大、持久化存储,适用于大文件或跨站点缓存,浏览器会根据文件大小和内存占用自动选择存储位置(例如大文件优先存入磁盘)。
三、协商缓存:服务器参与的 “智能验证”
1. Last-Modified 与 If-Modified-Since:基于时间的验证
- 响应头 Last-Modified:标识资源最后修改时间,例如:
Last-Modified: Mon, 10 Jun 2024 12:00:00 GMT - 请求头 If-Modified-Since:浏览器携带上次获取的 Last-Modified 值,服务器对比当前资源修改时间,若未变化则返回 304。
2. ETag 与 If-None-Match:基于内容的指纹验证
- 响应头 ETag:通过哈希算法(如 MD5)生成资源内容的唯一标识,例如:
ETag: "5f3b2a1e1c4b3a2d4c5a" - 请求头 If-None-Match:浏览器携带上次获取的 ETag 值,服务器对比当前资源哈希,一致则返回 304。
- 精度更高,仅内容变化时 ETag 才会改变;
- 优先级高于 Last-Modified,两者同时存在时以 ETag 为准。
3. 协商缓存的 Express 实现示例
const express = require('express');
const fs = require('fs');
const crypto = require('crypto');
const app = express();
const md5 = (data) => crypto.createHash('md5').update(data).digest('hex');
app.get('/static/:file', (req, res) => {
const file = `./static/${req.params.file}`;
fs.readFile(file, 'utf8', (err, data) => {
if (err) return res.status(500).send('错误');
// 生成ETag
const currentEtag = md5(data);
// 获取浏览器携带的ETag
const ifNoneMatch = req.headers['if-none-match'];
if (ifNoneMatch === currentEtag) {
res.status(304).end(); // 资源未更新,返回304
return;
}
// 资源更新,返回新内容和ETag
res.setHeader('ETag', currentEtag);
res.send(data);
});
});
app.listen(3031); 四、缓存策略的最佳实践
- 静态资源缓存策略:
- CSS、JS、图片等长期不变资源:设置cache-control: public, max-age=31536000(1 年),并通过版本号(如app.v1.0.js)控制更新;
- 频繁更新的资源:设置max-age=3600(1 小时),兼顾性能与更新效率。
- 动态资源缓存策略:
- 不常变化的动态数据:使用private, max-age=60(1 分钟),减少重复请求;
- 实时数据:设置no-cache或no-store,强制每次请求服务器。
- 缓存优先级与调试:
- 强缓存 > 协商缓存 > 重新请求;
- 浏览器调试时按Ctrl+Shift+R(强制刷新)可绕过所有缓存,便于验证更新。
五、总结
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!