我用7天把91网的体验拆开:最关键的居然是缓存管理

引子 我用一周时间把91网的用户体验从“还行”推进到“舒服”,过程像解一道拆装题:每一件零件都重要,但最关键的,竟然是缓存管理。下面把我的7天拆测顺序、具体操作和能直接落地的策略写清楚,给做产品或前端优化的朋友参考。
Day 0 — 起点和衡量标准 先量化:把目标和指标列清楚,方便对照效果。我的关注点:
- 首屏可见时间(FCP/LCP)
- 交互可用时间(TTI)
- 首次字节时间(TTFB)
- 总体页面体积和请求数 工具:Chrome DevTools、Lighthouse、WebPageTest、Charles/浏览器Network面板、CDN日志
基线数据(示例)
- LCP ≈ 4.6s
- TTI ≈ 8.2s
- 页面大小 ≈ 3.5 MB
- 请求数 ≈ 78
Day 1 — 切片分析,锁定瓶颈 把页面拆成三类资源:
- 可长期缓存(静态资源:指纹化的 JS/CSS/图片)
- 可短期缓存(页面 HTML、API 响应)
- 不缓存或必须实时刷新(个性化数据、会话类 API)
发现的问题:
- 静态资源没有合理设置 Cache-Control,CDN 也未开启边缘长缓存
- 页面 HTML 的缓存策略混乱,导致频繁回源
- 客户端缺少统一的缓存策略(没有 Service Worker 或 SW 策略混乱)
Day 2 — 静态资源:指纹 + 长缓存 目标:让绝大多数静态资源在浏览器和 CDN 中长期命中,减少请求数和回源。
具体做法:
- 资源指纹(filename.[contenthash].js)
- 静态资源(指纹后)设置 Cache-Control: public, max-age=31536000, immutable
- 在 CDN 层开启边缘缓存(edge cache)并同步 ttl
- HTML、API 等设置较短的 max-age 或 no-cache
为什么有效: 指纹 + immutable 可以把浏览器缓存时间拉到一年,CDN 边缘缓存减少源站压力,大幅提升命中率和降低延迟。
Day 3 — HTML 和 API 缓存策略:分层与失效 重点:HTML 和个性化数据不能像静态资源那样长缓存,采用分层缓存和部分缓存技术。
策略:
- 页面骨架/模板采用边缘渲染或 SSR + 缓存(短 TTL 或基于标签的缓存失效)
- 个性化片段通过 JS 异步拉取并单独缓存(localStorage/IndexedDB + SW)
- 对 API 使用 ETag/Last-Modified 做条件请求,减少传输带宽
示例 header:
- HTML: Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=30
- API(共通):Cache-Control: private, max-age=0, must-revalidate 或 使用 ETag
Day 4 — 引入 Service Worker:离线友好与运行时缓存 把 Service Worker 当作“高级缓存控制器”来用,不是简单为离线而离线。
常用策略:
- precache(关键静态资源,在安装阶段缓存)
- runtime cache(图片、第三方库使用 cache-first 或 stale-while-revalidate)
- network-first 用于需要实时性的 API;cache-first 或 stale-while-revalidate 用于媒体和库文件
示例(伪代码): // precache self.addEventListener('install', event => { event.waitUntil( caches.open('precache-v1').then(cache => cache.addAll(['/app.css', '/app.js', '/logo.png'])) ); }); // runtime self.addEventListener('fetch', event => { const url = new URL(event.request.url); if (url.origin === location.origin && url.pathname.startsWith('/api/')) { event.respondWith(networkFirst(event.request)); } else { event.respondWith(cacheFirst(event.request)); } });
注意点:
- 对于登录态强相关接口,尽量 network-first
- 第三方脚本/广告资源放到单独的策略里,避免污染主缓存
Day 5 — 缩小负担:压缩、合并与懒加载 缓存放得再好,首屏体积和阻塞加载还是需要解决。
优化项:
- 启用 gzip 或 brotli 压缩
- 开启 HTTP/2 或 HTTP/3 多路复用
- 按需加载与代码分割(Critical CSS inline)
- 图片使用 modern 格式(WebP/AVIF),并做 srcset/responsive 处理
- 使用 rel=preload 提升关键资源优先级
Day 6 — 缓存失效与版本管理:不可忽视的细节 缓存管理靠得住的关键在于“可控失效”。
做法:
- 任何能变的静态资源都用指纹,变更即换名,浏览器自动更新
- HTML/模板变更触发 CDN purge 或基于内容 hash 的 s-maxage
- 建立自动化:CI 在部署时触发 CDN 缓存清理 APIs(按路由或标签)
- 对于大范围热更新,采用蓝绿/灰度发布减少缓存刷新冲击
Day 7 — 监控与回归测试:把一切变成可观测 优化不是一次性,需持续验证。
监控清单:
- Lighthouse 报表(LCP, TTI, CLS)
- CDN hit/miss 比例与回源流量
- 关键页面的真实用户监测(RUM):收集 LCP/TTI/TBT 分布
- 部署后 24/48/72 小时的回归测试(自动化)
最终效果(我的实测) 经过这套流程后,观测到的效果(示例数据):
- LCP 从约 4.6s 降到约 1.9s
- TTI 从 8.2s 降到 3.5s
- 页面请求数从 78 减到约 34
- 回源流量显著下降,CDN 命中率从 ~60% 提升到 ~92%
落地操作清单(可直接执行)
- 为静态资源启用内容指纹,配置 Cache-Control: public, max-age=31536000, immutable
- 页面 HTML 设置 s-maxage 与 stale-while-revalidate,结合 CDN edge cache
- 为关键静态资源做 precache(Service Worker),并为 API 设计 network-first 策略
- 配置 ETag/Last-Modified,对非个性化响应采用条件请求
- 自动化部署时触发 CDN 清理或按路径失效
- 监控:在页面上埋 RUM(LCP/TTI),定期跑 Lighthouse 脚本
结语 把体验拆开看,缓存管理不是单纯把东西放缓存那么简单,而是设计一个分层、可控、自动化的缓存体系:指纹 + 长缓存给静态资源;短 TTL + 条件请求给动态资源;Service Worker 做运行时补充。只要把这三层搭好,用户感觉到的快,开发和运维感受到的稳定,就会一起到来。