我用7天把91网的体验拆开:最关键的居然是缓存管理(信息量有点大)

前言
我用7天时间把91网从用户体验到后端响应做了系统拆解与优化。结果出乎不少人意料 —— 除了前端静态资源和网络延迟,真正对感知速度与稳定性影响最大的,竟然是缓存管理的好坏。下面把这次拆解的方法、发现、具体改动和可复用的实践全部写清楚,方便直接落地。
整体方法论(7天拆解思路)
- Day 1 — 基线与量化:采集 Lighthouse、WebPageTest、Chrome DevTools、后端APM、Redis/DB指标,确定关键SLA(TTFB、FCP、LCP、TTI、错误率)。
- Day 2 — 前端剖析:资源列表、缓存策略(Cache-Control、ETag)、打包指纹、图片和字体加载方式。
- Day 3 — CDN与边缘配置:静态/动态缓存规则、边缘TTL、缓存键与Vary头。
- Day 4 — 后端缓存层:应用层缓存(Redis/内存)、数据库查询缓存、缓存失效模式。
- Day 5 — 并发与故障场景:缓存穿透/击穿/雪崩、缓存一致性、短时高并发压测。
- Day 6 — 服务端与客户端协同:Service Worker、离线策略、stale-while-revalidate、降级策略。
- Day 7 — 验证与回归:A/B对比、回放流量、监控告警与Runbook完善。
关键工具
- Lighthouse / WebPageTest / Chrome DevTools(前端指标)
- curl、wrk、ab(负载与HTTP行为)
- Redis-cli、slowlog(缓存与命中率)
- Prometheus + Grafana / New Relic(指标与告警)
- git、CI流水线(发布与回滚)
核心发现(简要数据示例)
- 初始基线:中等页面 LCP ~2.8s,TTFB ~400ms,缓存命中率(静态)约65%,API层Redis命中率约45%。
- 针对性优化后(主要在缓存策略和缓存逻辑):LCP下降到1.4s,TTFB下降到180ms,静态缓存命中率提升到95%,API层Redis命中率提升到85%,后端DB QPS下降约60%。
这些数字来自七天内多次对比采集,说明缓存策略改变对感知和成本的双重影响非常大。
为什么缓存管理比你想的更关键
- 缓存决定了请求命中边缘还是回到原点:边缘命中能把延迟从数百毫秒直接砍成几十毫秒。
- 不合理的TTL或不当的失效策略会把缓存变成“刀锋”——短时间内大并发都会击穿,带来数据库雪崩。
- 缓存命中率直接和成本相关:高命中率能显著减少后端算力与流量开销。
- 客户感知不只是静态资源,API响应的稳定与一致性会直接影响首屏和交互感受。
具体改动清单(可复制落地)
1) 静态资源策略
- 版本化文件名(fingerprint)+ Cache-Control: public, max-age=31536000, immutable 用于js/css/img经指纹化的资源。
- HTML(入口页)用短TTL(例如 60s)+ stale-while-revalidate=30s,这样用户基本上看到最新HTML同时能用后端异步刷新缓存。
- 在HTTP头引入 Vary: Accept-Encoding 避免压缩缓存污染。
2) CDN/边缘优化
- 区分资源类型建立不同TTL:静态资源长TTL,API短TTL或不缓存。
- 启用 stale-while-revalidate / stale-if-error 来提高可用性与降低延迟抖动。
- 缓存键中避免包含不必要的Cookie、Authorization等;为用户个性化内容使用路由前置或分片键。
- 实现按标签的批量清除(Tag-based purge),避免全量失效。
3) API与后端缓存
- 采取 Cache-Aside(先查缓存,未命中再回源并写缓存)为主,结合短TTL与合理缓存粒度。
- 对热点数据使用固定TTL + 定期预热(cache warming)。
- 对复杂查询与聚合结果考虑物化视图或预计算并缓存。
- Redis配置:合理内存策略(volatile-lru/volatile-ttl),监控eviction和memory_used。
4) 防止击穿/穿透/雪崩
- 击穿:使用分布式锁/请求合并(例如 Redis SETNX 进行单次回源构建),或者使用互斥缓存层(singleflight)。
- 穿透:对不合法key做黑名单或布隆过滤器,避免无效请求打到DB。
- 雪崩:不同key错峰TTL(TTL抖动),分布式限流和熔断降级策略。
5) 前端缓存 & Service Worker
- 对静态资源用 Cache-first 策略;对接口用 Network-first + fallback cache(保证离线或网络抖动时的基本体验)。
- 对大图片使用 lazy-loading 与优先加载关键首屏资源。
- Service Worker 中实现 stale-while-revalidate:先返回缓存,再后台更新。
6) 缓存无效与一致性
- 对强一致需求使用短TTL或基于事件驱动的主动失效(例如发布时通过消息队列触发分布式清除)。
- 对几乎不变的内容采用版本号或Tag机制,做到精确清除。
观测与告警(不可缺)
- 指标:cachehitratio、redisevictions、avgttfb、95/99延迟、originqps、errorrate。
- 建议设置阈值告警:cachehitratio骤降、redisevictions上升、originqps突然跳升。
- 日志里记录cache命中/未命中的上下文,方便定位是key设计问题还是TTL问题。
简短示例(HTTP头与Redis伪码)
- 静态资源头示例:
Cache-Control: public, max-age=31536000, immutable
- HTML示例:
Cache-Control: public, max-age=60, stale-while-revalidate=30
- Redis缓存伪码(cache-aside):
val = redis.get(key)
if val: return val
lock = redis.setnx(lockKey, 1, expire=5)
if lock:
val = computeFromDB()
redis.set(key, val, ex=TTL)
redis.del(lockKey)
return val
else:
wait and retry或返回降级值
优先级清单(执行顺序建议)
1) 立即:为静态指纹化资源设置长缓存并启用CDN。
2) 48小时:给HTML/入口页加短TTL + stale-while-revalidate。
3) 72小时:统计API层命中率,补上Cache-Aside逻辑与热点预热。
4) 1周内:加入击穿/雪崩保护(分布式锁/TTL抖动)。
5) 持续:建立监控Dashboard与报警,定期review缓存策略。
总结
把体验拆开来看,你会发现“一个看似小的缓存配置”能带来连锁反应:更低的延迟、更少的后端负载、更稳定的峰值表现,还能降低成本。缓存不是放着就完事儿的装饰,而是产品级别体验与可靠性的核心部分。把缓存管理当成工程的一等公民,短期你会看到明显指标改善,中长期你会少很多烧夜抢修的事故。
如果你愿意,我可以把我在这7天里记录的Lighthouse报告差异、Redis命中率变化图和具体Header配置打包成一份可直接导入到CI的配置清单,便于在你站点上快速复现。要不要我把这套“落地清单”整理成一页步骤文档?