acongm

从性能优化到 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和ETag

3. 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. 渐进式优化:
   - 分阶段实施
   - 验证每阶段效果
   - 及时调整策略

On this page