从性能优化到 HTTP 变化及缓存
笔试题(6 题)
1. HTTP/1.1 vs HTTP/2 vs HTTP/3
对比 HTTP/1.1、HTTP/2、HTTP/3 的核心差异,以及它们对前端性能优化的影响。
【作答】:
HTTP/1.1:
核心特性:
- 持久连接(Connection: keep-alive),减少连接建立开销
- 管道化(pipelining),但存在队头阻塞问题
- 支持分块传输编码(chunked transfer encoding)
- 支持缓存控制(Cache-Control、ETag等)
性能瓶颈:
- 队头阻塞(Head-of-Line Blocking):同一连接上请求必须按顺序响应
- 每个域名最多6个并发连接限制
- 请求头冗余,每次请求都携带完整头部
- 不支持服务器推送
HTTP/2:
核心特性:
- 多路复用(Multiplexing):单连接上并行处理多个请求/响应
- 二进制分帧:将数据分割为更小的帧,提高传输效率
- 头部压缩(HPACK):减少头部大小
- 服务器推送(Server Push):主动推送资源给客户端
- 流优先级:支持请求优先级设置
性能提升:
- 消除队头阻塞(应用层)
- 减少连接数,降低延迟
- 头部压缩可减少30-50%的头部大小
- 多路复用提高带宽利用率
HTTP/3:
核心特性:
- 基于UDP的QUIC协议,而非TCP
- 内置加密(TLS 1.3)
- 连接迁移:网络切换时保持连接
- 0-RTT连接建立(已连接过的服务器)
- 解决传输层队头阻塞问题
性能提升:
- 消除TCP层队头阻塞
- 更快的连接建立(0-RTT或1-RTT)
- 更好的移动网络体验(连接迁移)
- 减少延迟和丢包影响
对前端优化策略的影响:
HTTP/1.1时代:
- 需要合并文件、使用雪碧图、域名分片
- 内联关键CSS/JS
- 减少HTTP请求数
HTTP/2时代:
- 可以拆分文件,利用多路复用
- 不再需要雪碧图,可以单独请求图片
- 域名分片可能反而降低性能
- 服务器推送可以主动推送关键资源
HTTP/3时代:
- 进一步优化移动端体验
- 更快的连接建立,减少首次加载时间
- 更好的网络切换体验2. HTTP 缓存机制
HTTP 缓存机制详解:强缓存(Expires、Cache-Control)vs 协商缓存(ETag、Last-Modified)。
【作答】:
强缓存:
Expires:
- HTTP/1.0的缓存控制,指定资源的过期时间(绝对时间)
- 格式:Expires: Wed, 21 Oct 2025 07:28:00 GMT
- 问题:依赖客户端时间,时间不准确会导致缓存失效
- 优先级低于Cache-Control
Cache-Control:
- HTTP/1.1的缓存控制,更灵活强大
- 常用指令:max-age、no-cache、no-store、public、private
- 示例:Cache-Control: max-age=3600, public
- 优先级高于Expires
协商缓存:
ETag:
- 实体标签,资源的唯一标识符(通常是内容的hash值)
- 格式:ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
- 客户端请求时携带If-None-Match头
- 服务器比较ETag,相同返回304,不同返回200和新ETag
- 优点:精确,能检测到内容变化
- 缺点:需要计算hash,消耗服务器资源
Last-Modified:
- 资源最后修改时间
- 格式:Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
- 客户端请求时携带If-Modified-Since头
- 服务器比较时间,未修改返回304,已修改返回200
- 优点:实现简单
- 缺点:精度到秒,1秒内多次修改无法检测;文件内容未变但时间变了会失效
缓存流程:
1. 浏览器发起请求
2. 检查强缓存(Cache-Control/Expires)
- 未过期:直接使用缓存,状态码200(from cache)
- 已过期:进入下一步
3. 检查协商缓存(ETag/Last-Modified)
- 发送If-None-Match(ETag)或If-Modified-Since(Last-Modified)
- 服务器验证资源是否变化
- 未变化:返回304 Not Modified,使用缓存
- 已变化:返回200和新资源
优先级:
1. Cache-Control > Expires(强缓存)
2. ETag > Last-Modified(协商缓存)
3. 强缓存 > 协商缓存
4. 如果同时设置,浏览器优先使用Cache-Control和ETag3. Cache-Control 指令
Cache-Control 常用指令有哪些?max-age、no-cache、no-store、public、private 的区别?
【作答】:
max-age:
- 指定资源可以被缓存的最大时间(秒)
- 示例:Cache-Control: max-age=3600(缓存1小时)
- 相对时间,不依赖客户端时钟
- 从响应时间开始计算
no-cache:
- 不是"不缓存",而是"使用前必须验证"
- 每次使用缓存前都要向服务器验证(发送协商缓存请求)
- 适用于需要实时性但变化不频繁的资源
- 示例:Cache-Control: no-cache
no-store:
- 真正的不缓存,禁止任何缓存
- 不存储到浏览器缓存、CDN、代理服务器等任何地方
- 适用于敏感数据(密码、支付信息等)
- 示例:Cache-Control: no-store
public:
- 允许任何缓存(浏览器、CDN、代理服务器)缓存资源
- 适用于可以被公开缓存的资源(图片、字体、静态文件等)
- 示例:Cache-Control: public, max-age=31536000
private:
- 只允许浏览器缓存,不允许CDN、代理服务器缓存
- 适用于用户个性化内容(用户信息、购物车等)
- 示例:Cache-Control: private, max-age=3600
组合使用场景:
1. 静态资源(JS/CSS/图片):
Cache-Control: public, max-age=31536000, immutable
- 长期缓存,使用文件hash确保更新
2. HTML文件:
Cache-Control: no-cache
- 每次验证,确保获取最新版本
3. API响应(用户数据):
Cache-Control: private, max-age=300
- 短期缓存,仅浏览器缓存
4. 敏感数据:
Cache-Control: no-store, no-cache, must-revalidate
- 完全不缓存
5. 可缓存但需验证:
Cache-Control: public, max-age=3600, must-revalidate
- 缓存1小时,过期后必须验证
6. 其他指令:
- must-revalidate:过期后必须验证,不能使用过期缓存
- immutable:资源永不过期,适用于带hash的文件
- s-maxage:CDN/代理服务器的缓存时间
- stale-while-revalidate:允许使用过期缓存,同时后台更新4. 缓存策略设计
如何为不同资源设计缓存策略(HTML、JS/CSS、图片、API)?文件指纹(hash)的作用?
【作答】:
HTML:
策略:no-cache 或 max-age=0, must-revalidate
原因:
- HTML是入口文件,必须保证获取最新版本
- HTML中引用的资源URL会变化(带hash),需要及时更新
- 使用no-cache确保每次验证,但304响应仍可使用缓存
实践:
- 设置较短的max-age(如300秒),过期后验证
- 或使用no-cache,配合ETag进行协商缓存
JS/CSS:
策略:public, max-age=31536000, immutable(配合文件hash)
原因:
- 静态资源变化频率低,适合长期缓存
- 使用文件hash(contenthash)确保内容变化时URL变化
- immutable告诉浏览器资源永不过期,避免不必要的验证请求
实践:
- 构建时生成hash:app.abc123.js
- 设置长期缓存(1年)
- HTML中引用带hash的URL,HTML更新时自动获取新资源
图片/字体:
策略:public, max-age=2592000(30天)
原因:
- 图片和字体文件较大,适合缓存
- 变化频率较低
- 可以设置较长的缓存时间
实践:
- 使用CDN加速
- 设置合理的max-age(1-6个月)
- 对于可能更新的图片,使用版本号或hash
API:
策略:根据数据类型选择
- 用户数据:private, max-age=0, must-revalidate
- 公共数据:public, max-age=300(5分钟)
- 实时数据:no-store 或 no-cache
原因:
- API数据变化频繁,需要根据业务场景设置
- 用户个性化数据不能公开缓存
- 需要平衡实时性和性能
实践:
- 使用ETag进行协商缓存
- 设置合理的缓存时间
- 考虑使用SWR(stale-while-revalidate)策略
文件指纹 (hash):
作用:
1. 内容变化时URL变化,强制浏览器获取新资源
2. 实现长期缓存:URL不变时使用缓存,URL变化时获取新资源
3. 解决缓存更新问题:无需手动清理缓存
4. 支持并行部署:新旧版本可以同时存在
实现方式:
- Webpack: [name].[contenthash:8].js
- Vite: 自动生成hash文件名
- 手动:version?v=1.2.3(版本号,不如hash精确)
最佳实践:
- JS/CSS使用contenthash(基于文件内容)
- 图片可以使用hash或版本号
- HTML中引用带hash的资源URL
- 配合immutable指令,避免不必要的验证请求5. Service Worker 缓存
Service Worker 缓存与 HTTP 缓存的区别?SW 缓存策略有哪些(CacheFirst、NetworkFirst)?
【作答】:
区别:
1. 控制层面:
- HTTP缓存:浏览器自动管理,通过HTTP头控制
- SW缓存:JavaScript代码控制,更灵活
2. 缓存位置:
- HTTP缓存:浏览器缓存(内存/磁盘)
- SW缓存:Cache API,独立存储空间
3. 缓存策略:
- HTTP缓存:基于HTTP头的规则(强缓存/协商缓存)
- SW缓存:可编程策略(CacheFirst、NetworkFirst等)
4. 离线能力:
- HTTP缓存:需要网络连接验证
- SW缓存:完全离线可用
5. 更新控制:
- HTTP缓存:依赖HTTP头和浏览器行为
- SW缓存:完全由代码控制更新逻辑
6. 适用场景:
- HTTP缓存:所有HTTP请求
- SW缓存:需要离线支持、复杂缓存逻辑的场景
CacheFirst:
策略:优先使用缓存,缓存未命中时请求网络
流程:
1. 检查SW缓存
2. 命中:直接返回缓存
3. 未命中:请求网络,缓存响应后返回
适用场景:
- 静态资源(图片、字体、JS/CSS)
- 不常变化的资源
- 离线优先的场景
优点:快速响应,节省带宽
缺点:可能返回过期数据
NetworkFirst:
策略:优先请求网络,失败时使用缓存
流程:
1. 请求网络
2. 成功:更新缓存,返回响应
3. 失败:检查缓存,返回缓存或错误
适用场景:
- API请求
- 需要实时性的数据
- 网络优先的场景
优点:数据较新,有离线降级
缺点:网络慢时响应慢
StaleWhileRevalidate:
策略:立即返回缓存,同时后台更新缓存
流程:
1. 立即返回缓存(即使过期)
2. 后台请求网络更新缓存
3. 下次请求使用新缓存
适用场景:
- 需要快速响应但允许数据稍旧
- 新闻、博客等可以接受短暂延迟的内容
- 平衡性能和实时性
优点:快速响应,后台更新
缺点:首次可能返回过期数据
其他策略:
1. NetworkOnly:
- 只使用网络,不使用缓存
- 适用于必须实时获取的数据
2. CacheOnly:
- 只使用缓存,不请求网络
- 适用于完全离线的场景
3. 自定义策略:
- 根据URL、请求头等条件选择策略
- 例如:API用NetworkFirst,静态资源用CacheFirst
4. 组合策略:
- 不同资源使用不同策略
- 根据网络状态动态切换策略6. 缓存失效与更新
如何解决缓存更新问题?版本号、文件指纹、缓存清理策略?
【作答】:
缓存更新问题:
1. 强缓存导致资源不更新:
- 用户看到旧版本
- 新功能无法使用
- Bug修复不生效
2. 协商缓存验证开销:
- 每次请求都要验证
- 增加服务器压力
3. 多级缓存同步:
- 浏览器缓存、CDN缓存、服务器缓存
- 更新不同步导致问题
4. 部分资源更新:
- 只更新了部分文件
- 新旧版本混用导致错误
版本号方案:
实现方式:
- URL参数:app.js?v=1.2.3
- 路径版本:/v1.2.3/app.js
- 文件名版本:app-v1.2.3.js
优点:
- 实现简单
- 可以手动控制版本
- 适合小项目
缺点:
- 需要手动维护版本号
- 可能忘记更新版本
- 全量更新,即使文件未变化
- 版本号冲突风险
文件指纹方案:
实现方式:
- Content Hash:基于文件内容生成hash
- app.abc123def456.js(内容变化hash变化)
- 构建工具自动生成:
- Webpack: [name].[contenthash:8].js
- Vite: 自动处理
- HTML中自动引用带hash的URL
优点:
- 自动生成,无需手动维护
- 精确:只有内容变化时hash才变化
- 支持增量更新:只更新变化的文件
- 长期缓存:URL不变时使用缓存
缺点:
- 需要构建工具支持
- 首次构建可能较慢(计算hash)
- 需要配合HTML更新机制
缓存清理:
1. 浏览器缓存清理:
- 用户手动清理(Ctrl+Shift+Delete)
- 代码无法直接清理用户浏览器缓存
2. 服务器端清理:
- 更新资源URL(hash/版本号)
- 设置较短的缓存时间
- 使用Cache-Control: no-cache
3. CDN缓存清理:
- CDN控制台手动清理
- API接口批量清理
- 设置较短的s-maxage
4. Service Worker缓存清理:
- 代码控制,可以精确清理
- 监听更新事件,清理旧缓存
- 使用Cache API的delete方法
5. 版本化清理:
- 保留多个版本,逐步清理旧版本
- 避免清理正在使用的资源
最佳实践:
1. 分层缓存策略:
- HTML: no-cache(每次验证)
- JS/CSS: 长期缓存 + hash
- 图片: 中期缓存(1-6个月)
- API: 短期缓存 + 协商缓存
2. 文件指纹 + 长期缓存:
- 使用contenthash生成文件名
- 设置public, max-age=31536000, immutable
- HTML更新时自动获取新资源
3. 渐进式更新:
- 支持多版本共存
- 逐步清理旧版本
- 避免强制更新导致的问题
4. 监控和告警:
- 监控缓存命中率
- 监控资源更新情况
- 设置异常告警
5. 降级方案:
- 缓存失效时的降级策略
- 离线时的处理方案
- 网络异常时的用户体验
6. 测试验证:
- 测试缓存更新流程
- 验证新旧版本兼容性
- 检查缓存清理是否生效面试题(4 题)
1. 缓存策略优化
给一个网站设计完整的缓存策略,从 HTTP 缓存到 CDN 到 Service Worker,如何组合使用?
【作答】:
完整的多层缓存策略设计:
一、HTTP缓存层(浏览器缓存)
1. HTML文件:
Cache-Control: no-cache, must-revalidate
ETag: 启用
原因:入口文件,必须保证最新
2. JS/CSS文件(带hash):
Cache-Control: public, max-age=31536000, immutable
原因:内容hash确保更新,可长期缓存
3. 图片/字体:
Cache-Control: public, max-age=2592000
Last-Modified: 启用
原因:变化频率低,适合中期缓存
4. API响应:
- 用户数据:Cache-Control: private, max-age=0
- 公共数据:Cache-Control: public, max-age=300
- 实时数据:Cache-Control: no-store
二、CDN缓存层
1. 静态资源(JS/CSS/图片):
- 缓存时间:1年
- 回源验证:使用ETag/Last-Modified
- 边缘节点缓存,减少源站压力
2. HTML文件:
- 缓存时间:5分钟
- 回源验证:每次验证
- 确保及时更新
3. API:
- 根据业务决定是否CDN缓存
- 通常不缓存,直接回源
三、Service Worker缓存层
1. 静态资源策略:
- CacheFirst + 长期缓存
- 离线可用
- 后台更新
2. API策略:
- NetworkFirst + 短期缓存
- 离线降级
- 后台同步
3. HTML策略:
- NetworkFirst + 短期缓存
- 确保获取最新版本
四、组合使用方案
1. 首次访问:
- 请求 → CDN(未命中)→ 源站
- 响应设置HTTP缓存头
- SW缓存关键资源
2. 再次访问(在线):
- 检查HTTP缓存(强缓存)
- 检查SW缓存
- 检查CDN缓存
- 按优先级返回
3. 离线访问:
- SW缓存提供服务
- 显示离线提示
- 后台同步数据
4. 更新流程:
- HTML更新(no-cache)
- 新HTML引用新hash的资源
- 旧资源继续缓存(多版本共存)
- SW后台更新缓存
五、实际配置示例
Nginx配置:
location ~* \.(js|css)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header ETag on;
}
location ~* \.(jpg|png|gif|woff|woff2)$ {
add_header Cache-Control "public, max-age=2592000";
}
location = /index.html {
add_header Cache-Control "no-cache, must-revalidate";
}
Service Worker示例:
// 静态资源:CacheFirst
workbox.routing.registerRoute(
/\.(?:js|css|woff2?)$/,
new workbox.strategies.CacheFirst({
cacheName: 'static-resources',
plugins: [{
cacheableResponse: { statuses: [0, 200] },
expiration: { maxEntries: 100, maxAgeSeconds: 31536000 }
}]
})
);
// API:NetworkFirst
workbox.routing.registerRoute(
/\/api\//,
new workbox.strategies.NetworkFirst({
cacheName: 'api-cache',
plugins: [{
expiration: { maxEntries: 50, maxAgeSeconds: 300 }
}]
})
);2. HTTP/2 优化策略变化
HTTP/2 下,传统的性能优化策略(合并文件、雪碧图、域名分片)哪些失效了?为什么?
【作答】:
HTTP/2带来的变化导致以下传统优化策略失效或不再必要:
一、失效的策略
1. 文件合并(Concatenation)
失效原因:
- HTTP/1.1:每个文件需要单独请求,合并减少请求数
- HTTP/2:多路复用,单连接并行请求,请求数不再是瓶颈
- 合并文件反而带来问题:
* 缓存粒度粗:一个文件变化,整个合并文件失效
* 并行加载受限:无法利用HTTP/2的多路复用优势
* 代码分割困难:难以按需加载
建议:
- 拆分文件,利用HTTP/2多路复用
- 使用代码分割,按需加载
- 保持文件独立性,提高缓存效率
2. 雪碧图(Sprite)
失效原因:
- HTTP/1.1:减少图片请求数
- HTTP/2:多路复用,图片请求并行,不再需要合并
- 雪碧图的问题:
* 维护困难:添加/删除图片需要重新生成
* 缓存效率低:一个图片变化,整个雪碧图失效
* 无法利用HTTP/2的并行加载优势
* 响应式适配困难
建议:
- 使用单独的图片文件
- 利用HTTP/2并行加载
- 使用WebP等现代图片格式
- 考虑使用SVG图标
3. 域名分片(Domain Sharding)
失效原因:
- HTTP/1.1:每个域名最多6个并发连接,分片增加连接数
- HTTP/2:单连接多路复用,多个域名反而增加开销
- 域名分片的问题:
* DNS查询开销:多个域名需要多次DNS查询
* 连接建立开销:每个域名需要建立连接
* 无法利用HTTP/2的单连接优势
* 可能降低性能
建议:
- 使用单一域名或少量域名
- 利用HTTP/2的单连接多路复用
- 减少DNS查询和连接建立开销
4. 内联关键资源(Inline Critical Resources)
部分失效:
- HTTP/1.1:内联关键CSS/JS减少请求
- HTTP/2:多路复用使内联的必要性降低
- 但仍有一定价值:
* 减少关键路径的RTT
* 避免阻塞渲染的资源请求
建议:
- 可以继续内联关键CSS(首屏渲染)
- JS内联的必要性降低
- 平衡内联和缓存的关系
二、仍然有效的策略
1. 资源压缩(Gzip/Brotli)
- HTTP/2头部压缩不影响内容压缩
- 继续使用Gzip/Brotli压缩资源
2. 图片优化
- 使用WebP、AVIF等现代格式
- 响应式图片(srcset)
- 懒加载
3. 代码分割
- 按路由分割
- 按需加载
- 利用HTTP/2并行加载优势
4. CDN使用
- 地理分布
- 边缘缓存
- 仍然有效
5. 缓存策略
- HTTP缓存头
- 文件hash
- 仍然重要
三、新的优化策略
1. 服务器推送(Server Push)
- HTTP/2新特性
- 主动推送关键资源
- 减少RTT
2. 资源优先级(Priority)
- 设置资源加载优先级
- 优化关键路径
3. HTTP/2特定的优化
- 利用多路复用拆分资源
- 利用头部压缩减少开销
- 利用服务器推送预加载
四、实际建议
1. 迁移策略:
- 评估当前优化策略
- 逐步移除失效策略
- 测试性能影响
2. 混合策略:
- 考虑HTTP/1.1用户(降级)
- 为HTTP/2用户优化
- 使用特性检测
3. 监控和测试:
- 监控HTTP版本分布
- A/B测试优化效果
- 持续优化3. 缓存问题排查
用户反馈"看到的是旧版本",你如何排查缓存问题?从浏览器、CDN、服务器多个层面分析。
【作答】:
系统化的缓存问题排查流程:
一、信息收集阶段
1. 收集用户信息:
- 浏览器类型和版本
- 访问的URL
- 具体看到的内容(截图)
- 是否强制刷新(Ctrl+F5)后仍显示旧版本
- 其他用户是否也有此问题
- 问题出现时间
2. 检查服务器状态:
- 确认服务器已更新
- 检查部署日志
- 验证文件内容是否正确
二、浏览器层面排查
1. 开发者工具检查:
- Network面板:
* 查看资源状态码(200/304/from cache)
* 检查响应头(Cache-Control、ETag、Last-Modified)
* 查看资源大小和时间戳
* 确认资源URL是否正确(hash是否更新)
- Application面板:
* 检查Service Worker缓存
* 检查HTTP缓存
* 查看存储的缓存内容
2. 缓存验证:
- 硬刷新(Ctrl+Shift+R / Cmd+Shift+R)
- 清除缓存并硬性重新加载
- 无痕模式访问(排除扩展影响)
- 禁用Service Worker测试
3. 常见问题:
- 强缓存未过期:检查Cache-Control max-age
- Service Worker缓存:检查SW缓存策略
- 浏览器扩展缓存:禁用扩展测试
- 本地存储:检查localStorage/sessionStorage
三、CDN层面排查
1. CDN缓存检查:
- CDN控制台查看缓存状态
- 检查CDN缓存规则配置
- 查看CDN日志(命中/未命中)
- 检查CDN节点的缓存时间
2. CDN缓存清理:
- 手动清理特定URL
- 批量清理(按目录/文件类型)
- 检查清理是否生效
- 验证清理后的响应
3. CDN配置检查:
- 缓存时间设置(s-maxage)
- 回源验证配置
- 缓存键(Cache Key)配置
- 边缘节点同步状态
4. 多节点验证:
- 不同地区访问测试
- 检查边缘节点缓存一致性
- 验证回源策略
四、服务器层面排查
1. HTTP头检查:
- 验证Cache-Control设置
- 检查ETag生成是否正确
- 验证Last-Modified时间
- 确认响应头配置
2. 文件内容验证:
- 直接访问源站URL
- 检查文件内容是否更新
- 验证文件hash是否正确
- 检查文件时间戳
3. 服务器配置:
- Nginx/Apache缓存配置
- 反向代理缓存设置
- 应用层缓存(Redis等)
- 静态文件服务配置
4. 部署验证:
- 检查部署是否成功
- 验证文件是否覆盖
- 检查文件权限
- 确认多服务器同步
五、排查工具和方法
1. 浏览器工具:
- Chrome DevTools
- Firefox Developer Tools
- Safari Web Inspector
2. 命令行工具:
- curl -I(查看响应头)
- curl -v(详细请求信息)
- wget --spider(检查资源)
3. 在线工具:
- WebPageTest
- GTmetrix
- Pingdom
4. 日志分析:
- 服务器访问日志
- CDN日志
- 应用日志
六、问题定位流程
1. 快速验证:
1. 无痕模式访问 → 排除浏览器缓存
2. 直接访问源站 → 排除CDN缓存
3. 检查文件hash → 确认文件是否更新
4. 查看响应头 → 确认缓存配置
2. 逐步排查:
浏览器缓存 → CDN缓存 → 服务器缓存 → 源站文件
3. 验证修复:
清理缓存 → 验证响应 → 确认更新 → 监控效果
七、常见问题和解决方案
1. HTML未更新:
- 问题:HTML设置了强缓存
- 解决:改为no-cache或短时间缓存
2. JS/CSS未更新:
- 问题:文件hash未变化或缓存未清理
- 解决:确保hash更新,清理CDN缓存
3. 图片未更新:
- 问题:CDN缓存时间过长
- 解决:清理CDN缓存,调整缓存时间
4. Service Worker缓存:
- 问题:SW缓存了旧资源
- 解决:更新SW版本,清理旧缓存
5. 多级缓存不同步:
- 问题:浏览器/CDN/服务器缓存不一致
- 解决:统一缓存策略,清理所有缓存
八、预防措施
1. 合理的缓存策略:
- HTML: no-cache
- 静态资源: 长期缓存 + hash
- API: 短期缓存
2. 版本控制:
- 使用文件hash
- 确保hash唯一性
- 版本化部署
3. 监控告警:
- 监控缓存命中率
- 监控资源更新
- 设置异常告警
4. 文档和流程:
- 缓存策略文档
- 问题排查手册
- 应急处理流程4. 缓存实践经验
讲一个你优化缓存策略的经验:初始状态、优化方案、效果数据、遇到的坑?
【作答】:
项目背景:一个中大型React SPA应用,日活10万+,用户反馈加载慢、经常看到旧版本。
一、初始状态
问题表现:
1. 性能问题:
- 首屏加载时间:3.5秒
- 重复访问仍需2秒
- 大量304请求,浪费带宽
2. 缓存问题:
- 用户经常看到旧版本
- 更新后需要强制刷新才能看到新功能
- 客服收到大量"为什么看不到更新"的反馈
3. 技术现状:
- HTML: Cache-Control: max-age=3600(1小时强缓存)
- JS/CSS: 无缓存策略,每次请求
- 图片: 无统一缓存策略
- API: 无缓存
- 未使用文件hash
- 未使用CDN
- 未使用Service Worker
4. 数据指标:
- 缓存命中率:< 10%
- 平均请求数:45个/页面
- 平均传输大小:2.1MB
- 服务器带宽:高峰期500Mbps
二、优化方案
阶段一:基础HTTP缓存优化(1周)
1. HTML缓存策略:
- 改为:Cache-Control: no-cache, must-revalidate
- 启用ETag
- 确保每次验证,但304仍可使用缓存
2. 静态资源缓存:
- JS/CSS: 设置Cache-Control: public, max-age=86400(1天)
- 图片: Cache-Control: public, max-age=604800(7天)
- 字体: Cache-Control: public, max-age=2592000(30天)
3. API缓存:
- 公共数据: Cache-Control: public, max-age=300(5分钟)
- 用户数据: Cache-Control: private, max-age=0
效果:
- 缓存命中率提升到35%
- 重复访问时间减少到1.5秒
- 但仍有缓存更新问题
阶段二:文件hash + 长期缓存(2周)
1. 构建优化:
- 配置Webpack使用contenthash
- 文件名格式:[name].[contenthash:8].[ext]
- HTML中自动引用带hash的文件
2. 缓存策略调整:
- JS/CSS: Cache-Control: public, max-age=31536000, immutable
- 长期缓存,依赖hash确保更新
3. HTML策略:
- 保持no-cache
- 确保能获取最新版本
效果:
- 缓存命中率提升到65%
- 重复访问时间减少到0.8秒
- 缓存更新问题基本解决
阶段三:CDN + 进一步优化(3周)
1. CDN接入:
- 静态资源全部走CDN
- CDN缓存时间:1年(配合hash)
- HTML缓存:5分钟
2. 资源优化:
- 图片压缩和格式优化(WebP)
- Gzip/Brotli压缩
- 代码分割优化
3. Service Worker:
- 关键资源CacheFirst
- API NetworkFirst
- 离线支持
效果:
- 缓存命中率提升到85%
- 首屏加载时间:1.8秒
- 重复访问时间:0.3秒
- 离线可用
三、效果数据
优化前后对比:
1. 性能指标:
| 指标 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| 首屏加载 | 3.5s | 1.8s | 48% ↓ |
| 重复访问 | 2.0s | 0.3s | 85% ↓ |
| 缓存命中率 | 10% | 85% | 750% ↑ |
| 平均请求数 | 45 | 12 | 73% ↓ |
| 传输大小 | 2.1MB | 0.8MB | 62% ↓ |
2. 服务器指标:
| 指标 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| 带宽使用 | 500Mbps | 150Mbps | 70% ↓ |
| 服务器负载 | 高 | 低 | 显著降低 |
| 304请求 | 大量 | 少量 | 大幅减少 |
3. 用户体验:
- 用户反馈加载慢的问题减少90%
- "看到旧版本"的反馈减少95%
- 用户满意度提升
4. 成本:
- CDN成本:+2000元/月
- 服务器成本:-5000元/月
- 净节省:3000元/月
四、遇到的坑
1. HTML缓存问题:
- 问题:初期HTML也设置了长期缓存,导致更新不及时
- 解决:改为no-cache,确保每次验证
- 教训:HTML必须保证能及时更新
2. 文件hash冲突:
- 问题:不同环境构建的hash不一致
- 解决:统一构建环境,使用CI/CD
- 教训:确保构建环境一致性
3. CDN缓存清理:
- 问题:紧急更新时CDN缓存未及时清理
- 解决:建立CDN缓存清理流程和工具
- 教训:需要快速清理机制
4. Service Worker更新:
- 问题:SW缓存了旧资源,更新不及时
- 解决:实现SW版本管理和缓存清理逻辑
- 教训:SW需要版本控制
5. 多版本共存:
- 问题:新旧版本资源混用导致错误
- 解决:确保HTML和资源版本匹配
- 教训:需要版本一致性检查
6. 移动端缓存:
- 问题:移动端浏览器缓存行为不同
- 解决:针对移动端调整缓存策略
- 教训:需要跨平台测试
7. API缓存误用:
- 问题:缓存了不应该缓存的用户数据
- 解决:仔细区分public/private数据
- 教训:API缓存需要谨慎设计
五、经验总结
1. 缓存策略要分层:
- 不同资源不同策略
- HTML入口必须可更新
- 静态资源长期缓存
2. 文件hash是关键:
- 解决缓存更新问题
- 支持长期缓存
- 确保版本一致性
3. 监控和测试:
- 持续监控缓存命中率
- 测试不同场景
- 建立告警机制
4. 文档和流程:
- 记录缓存策略
- 建立问题排查流程
- 团队知识共享
5. 渐进式优化:
- 分阶段实施
- 验证每阶段效果
- 及时调整策略