HTTP缓存机制
概念引入
想象你家里有一个零食柜(缓存)。早上你想吃饼干,如果柜子里有且没过期,直接拿(缓存命中);如果过期了,你得看看包装上的生产日期,打电话问超市这批货换没换(协商缓存);如果柜子里根本没有,只能去超市买(回源)。
HTTP缓存机制就是浏览器的"零食柜"——它决定了什么时候直接用本地资源,什么时候必须向服务器重新请求。对于广州飞翔科技(www.feixiang.net)这样日活百万的网站,合理的缓存策略能节省大量带宽和服务器压力。
核心内容
为什么需要缓存?
飞翔公司官网的main.css文件大小约150KB,每天有100万用户访问。如果没有缓存:
- 每天传输:
150KB × 1,000,000 = 150GB - 带宽费用惊人,服务器压力巨大
启用缓存后:
- 用户第二次访问直接从本地读取,延迟接近0ms
- 服务器只在新版本发布时传输一次
缓存命中(Cache Hit):请求的资源在缓存中且可用,直接使用,无需回源。 缓存未命中(Cache Miss):缓存中没有该资源,或资源已失效,必须向服务器重新请求。
浏览器缓存决策流程
以空少(前端)访问飞翔官网为例:
- 第一次打开
www.feixiang.net,浏览器没有缓存 → 回源获取style.css - 第二次打开,缓存还在有效期内 → 直接读取本地,速度飞快
- 第三天打开,缓存过期了 → 带上"身份证"(ETag)问服务器:"文件变了吗?" → 服务器说"没变" → 返回304,继续用本地缓存
强缓存:Cache-Control
强缓存是指浏览器在缓存有效期内,完全不与服务器通信,直接使用本地缓存。
核心响应头:Cache-Control(HTTP/1.1)和Expires(HTTP/1.0,已逐渐被取代)
HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: max-age=3600 ← 缓存1小时(3600秒)
/* style.css 内容 */
Cache-Control 常用指令详解:
| 指令 | 含义 | 飞翔公司应用场景 |
|---|---|---|
max-age=3600 | 缓存3600秒 | 官网CSS/JS文件 |
no-store | 禁止任何缓存,每次都回源 | 员工工资查询页面 |
no-cache | 可以缓存,但使用前必须验证 | 用户个人信息页 |
private | 仅浏览器可缓存,代理不可 | 员工OA系统页面 |
public | 浏览器和代理都可缓存 | 官网logo、公共图片 |
immutable | 资源永不变,有效期内不验证 | 带hash的打包JS文件 |
// 飞翔官网静态资源的强缓存配置
HTTP/1.1 200 OK
Content-Type: application/javascript
Cache-Control: public, max-age=31536000, immutable
// 1年不验证,因为文件名包含内容哈希:app.a3f2b1c.js
鸣哥(内容运营)问:"no-cache和no-store有什么区别?" 凌叔(运维)回答:"no-store是彻底不让存;no-cache是可以存,但每次用之前必须问服务器能不能用,就像你可以把备用钥匙放邻居那,但用之前必须打电话确认。"
协商缓存:验证机制
当强缓存过期后,浏览器进入协商缓存阶段——带着缓存的"身份信息"去问服务器:"我的这份还能用吗?"
方式一:Last-Modified + If-Modified-Since
// 第一次响应
HTTP/1.1 200 OK
Last-Modified: Wed, 21 Jun 2024 07:28:00 GMT
// 后续请求(缓存过期后)
GET /style.css HTTP/1.1
Host: www.feixiang.net
If-Modified-Since: Wed, 21 Jun 2024 07:28:00 GMT
// 如果文件未修改
HTTP/1.1 304 Not Modified
// 如果文件已修改
HTTP/1.1 200 OK
Last-Modified: Thu, 22 Jun 2024 09:00:00 GMT
缺点:时间精度只能到秒;如果文件内容没变但重新生成,时间也会变。
方式二:ETag + If-None-Match(更精确)
ETag(Entity Tag):服务器为资源生成的唯一标识符,通常是文件内容的哈希值。
// 第一次响应
HTTP/1.1 200 OK
ETag: "33a64df5" ← 像资源的"指纹"
// 后续请求
GET /style.css HTTP/1.1
Host: www.feixiang.net
If-None-Match: "33a64df5" ← 带上指纹询问
// 如果指纹匹配(内容未变)
HTTP/1.1 304 Not Modified
// 如果指纹不匹配(内容已变)
HTTP/1.1 200 OK
ETag: "33a64df6" ← 新指纹
翼王(架构师)推荐:"飞翔官网统一用ETag,因为Last-Modified在CDN环境下容易出问题。"
强缓存 vs 协商缓存对比
| 对比项 | 强缓存 | 协商缓存 |
|---|---|---|
| 是否发请求到服务器 | 否 | 是(发验证请求) |
| 状态码 | 200 (from memory/disk cache)¹ | 304 Not Modified |
| 响应体 | 无(从缓存读) | 无(从缓存读) |
| 控制头 | Cache-Control/Expires | Last-Modified/ETag |
| 速度 | 极快(0ms网络延迟) | 较快(一次RTT) |
¹ 强缓存命中时浏览器不会向服务器发送请求,开发者工具中显示为
200 (from memory/disk cache)或200 (from disk cache),这是浏览器的本地标记,并非服务器返回的真实 HTTP 状态码。
浏览器缓存 vs 代理缓存
| 类型 | 位置 | 影响范围 | 典型代表 |
|---|---|---|---|
| 浏览器缓存 | 用户本地 | 仅当前用户 | Chrome磁盘缓存 |
| 代理缓存 | 网络中间节点 | 所有经过该代理的用户 | 公司网关、CDN节点 |
飞翔公司使用了阿里云CDN。靓晴(UI设计)更新了官网配色,但雁姐(用户运营)反馈"我这边看到还是旧的"。凌叔排查发现:CDN节点缓存了旧资源,需要手动刷新CDN缓存。
// 控制代理缓存的头部
Cache-Control: public, max-age=3600 ← 代理和浏览器都可缓存
Cache-Control: private, max-age=3600 ← 仅浏览器缓存
飞翔公司官网缓存策略实战
// 飞翔官网首页HTML
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: no-cache ← 每次用前验证,确保看到最新版
// 飞翔官网 main.css(带内容哈希)
HTTP/1.1 200 OK
Content-Type: text/css
Cache-Control: public, max-age=31536000, immutable
ETag: "a1b2c3d4"
// 飞翔官网用户数据API
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store, private ← 敏感数据,绝不缓存
本篇小结
- 缓存能显著减少延迟、节省带宽,是Web性能优化的核心手段
- 强缓存通过
Cache-Control: max-age等指令控制,有效期内完全不请求服务器 - 协商缓存在强缓存过期后触发,通过
ETag + If-None-Match或Last-Modified + If-Modified-Since验证资源是否变化 Cache-Control: no-store彻底禁用缓存;no-cache允许缓存但必须验证;private限制仅浏览器缓存;public允许代理缓存- 浏览器缓存只服务当前用户,代理缓存/CDN服务所有经过该节点的用户
- 飞翔官网策略:HTML用
no-cache、静态资源用长强缓存+immutable、API用no-store
动手实践
温馨提示: 以下实践示例中涉及的域名(如
www.feixiang.net)、公司场景和接口均为虚构数据,仅用于演示协议原理,实际执行时可能不会产生文档中描述的效果。建议将命令中的域名替换为你自己可访问的真实地址进行练习。
实践:用curl观察缓存头部
# 查看飞翔官网CSS的缓存策略
curl -I https://www.feixiang.net/static/css/main.css
# 观察响应头中的 Cache-Control、ETag、Last-Modified
实践:模拟协商缓存的304响应
# 第一次请求,记录ETag
curl -I https://www.feixiang.net/static/css/main.css
# 假设返回 ETag: "abc123"
# 第二次请求,带上If-None-Match
curl -I -H 'If-None-Match: "abc123"' https://www.feixiang.net/static/css/main.css
# 如果资源未变,应返回 304 Not Modified
实践:思考题
空少(前端)给飞翔官网的
app.js设置了Cache-Control: max-age=86400(1天)。第二天他紧急修复了一个线上bug并重新部署,但用户反馈问题依然存在。请分析原因,并给出更好的缓存策略方案。
实践:Chrome开发者工具观察缓存
- 打开Chrome,访问
www.feixiang.net - F12打开Network面板,勾选"Disable cache"对比两次刷新
- 观察Size列:
from disk cache、from memory cache、具体字节数的区别 - 点击单个请求,查看Response Headers中的缓存相关头部