Day31-14|10.6.3 大型网站系统架构演化实例(三):使用缓存改善网站性能

一、教材定位

大型网站在完成单体和垂直拆分后,常见的下一个瓶颈是数据库和后端服务无法承受不断增长的读请求。缓存通过把热点数据放在更接近访问者、速度更快的存储中,减少重复计算和后端访问,从而改善响应时间和吞吐量。教材中的这一阶段强调,缓存是架构演化的重要手段,但缓存并不是简单加一个中间件,它会引入一致性、失效、预热、热点、容量、穿透、击穿和雪崩等新问题。

考试中常见的错误是把“使用缓存”直接等同于“性能提升”。正确回答要说明哪些数据适合缓存、缓存放在哪里、如何设置键和过期时间、数据变更如何同步、缓存失效如何处理、热点和故障如何保护,以及缓存不一致对业务是否可接受。

二、为什么缓存能够改善性能

缓存通常具有更低的访问延迟和更高的吞吐,能够减少数据库磁盘访问、复杂查询、远程调用和重复计算。对于读多写少、重复访问、允许短时间旧数据或可以通过版本校验的数据,缓存收益明显。

缓存的收益可以从三个方面理解:减少后端请求量,降低单次请求成本,吸收短时流量峰值。缓存命中后,用户请求可能不再经过完整业务链路;但缓存未命中、数据失效和后端故障时,系统仍要具备正确的回源和保护机制。

三、缓存的层次

1. 浏览器和客户端缓存

适合静态资源、图片、脚本和不敏感的短期数据。优点是访问距离最近,不占用服务端资源;缺点是控制能力有限,数据更新和隐私清理较困难。应通过缓存控制、版本化文件名和敏感数据禁止缓存等方式治理。

2. 内容分发和边缘缓存

适合静态内容和部分可公开缓存的页面。它可以降低跨区域访问延迟和源站压力,但需要考虑缓存刷新、区域一致性、权限和个性化内容。用户身份、订单和支付等敏感内容不能因为追求命中率而错误缓存。

3. 反向代理缓存

位于客户端和应用之间,可以缓存页面、接口响应和静态资源。它还能够完成压缩、限流、路由和连接复用。设计时要正确区分请求参数、Cookie、认证头和缓存键,避免把一个用户的数据返回给另一个用户。

4. 应用本地缓存

数据保存在应用实例内存中,访问速度快且不需要网络,但每个实例可能有不同副本,扩容和重启会导致缓存丢失。适合配置、字典、热点只读数据和短生命周期对象,不适合要求全局一致的共享状态。

5. 分布式缓存

多个应用实例共享缓存集群,能够提高容量和一致性控制能力。需要考虑分片、副本、故障转移、网络延迟、连接池、序列化和数据淘汰。分布式缓存不可简单当作永久数据库使用,重要数据仍需有权威持久化来源。

6. 数据库缓存和物化视图

通过查询缓存、索引、物化结果和读副本减少计算与存储压力。它通常更接近数据源,适合复杂查询和报表,但刷新策略和更新延迟必须明确。

四、什么数据适合缓存

适合缓存的数据通常具有读多写少、计算成本高、访问重复、变化可预测、可容忍短暂旧值和明确生命周期等特征。商品详情、公开内容、地区字典、权限策略、汇率快照和搜索结果可能适合缓存。

不适合直接缓存的数据包括高度个性化、频繁变化、强一致交易状态、敏感凭证和无法安全隔离的数据。即使业务上必须快速访问,也应使用带版本、权限和一致性控制的专门方案。

判断缓存收益可以考虑命中率、回源成本、数据大小、更新频率、可接受陈旧时间和一致性风险。命中率高但数据错误代价巨大时,缓存可能不是好方案。

五、缓存读写模式

1. Cache Aside

应用先读缓存,命中则返回;未命中时读数据库,将结果写入缓存。写入时先更新数据库,再删除或更新缓存。该模式简单灵活,但并发更新时可能出现旧值回填,需要版本、延迟双删、互斥或消息失效等策略。

2. Read Through

应用只访问缓存层,缓存未命中时由缓存层负责加载数据。它减少业务代码重复,但要求缓存组件理解数据源和错误处理,适合统一读模型。

3. Write Through

写入先经过缓存并同步写入后端,缓存层保证两者更新。适合需要统一写入路径的场景,但对事务、失败和吞吐要求较高。

4. Write Behind

先写缓存,之后异步刷入持久化存储,能够提高写入吞吐,但存在数据丢失、一致性延迟和恢复复杂度。只有在业务能够容忍异步持久化和明确补偿时使用。

六、缓存一致性

1. 强一致和最终一致

强一致要求读取结果与权威数据严格同步,适合余额、库存可用量和支付状态等核心场景;最终一致允许短时间旧值,适合内容、推荐和非关键查询。应根据业务不变量选择,而不是统一采用一种模式。

2. 更新与删除

更新数据库后删除缓存通常比直接更新缓存更安全,因为业务规则可能有多个写入口。删除失败时要记录重试和补偿;直接更新缓存要保证序列化和并发顺序。

3. 版本和时间戳

缓存值可带版本、更新时间或逻辑时钟,读取和回填时拒绝旧版本覆盖新版本。对并发写入和跨区域同步,版本比较比简单的最后写入获胜更可控。

4. 消息失效

数据变更后发布失效事件,由订阅者删除或刷新缓存。消息必须可靠、幂等和可重放,事件丢失时要有定期校验或主动刷新机制。

七、缓存常见问题与治理

1. 缓存穿透

大量请求查询不存在的数据,缓存和数据库都被访问。可以缓存空值、使用布隆过滤器、校验输入和限制查询范围,但空值也要设置合理过期时间。

2. 缓存击穿

某个热点键过期瞬间,大量请求同时回源。可以使用互斥锁、逻辑过期、提前刷新、热点永不过期或请求合并,避免后端被同一热点压垮。

3. 缓存雪崩

大量键同时失效、缓存集群故障或网络异常导致请求集中回源。可以设置随机过期时间、分批预热、多级缓存、限流、降级和容量保护。

4. 热点和大键

单个热点键可能成为访问和网络瓶颈,大对象可能造成内存和序列化压力。可以拆分、复制、分片、压缩和限制对象大小,并监控访问分布。

5. 淘汰和容量

缓存应设置内存上限、淘汰策略和数据优先级。不能把缓存当作无限存储;对不同数据设置不同保留时间,并为核心键预留容量。

6. 缓存故障

缓存不可用时,系统不能无保护地全部回源。应根据业务设置限流、降级、过期数据读取、只读模式或临时关闭非核心功能。恢复后要控制预热速度,避免回源风暴。

八、缓存安全与运维

缓存键不能直接暴露敏感信息,值要按权限和租户隔离。用户相关缓存应包含身份范围,防止越权读取;敏感数据需要加密或不进入普通缓存。缓存管理操作要审计,禁止任意清空生产缓存。

运维要监控命中率、回源率、延迟、内存、淘汰、连接、热点、错误、失效事件和数据差异。命中率上升不一定代表系统健康,如果命中的是错误数据或过期数据,业务风险反而增加。

九、案例分析:商品网站缓存演化

某商品网站在垂直拆分后,商品详情和分类查询占据大量请求,数据库查询延迟上升。团队引入分布式缓存,初期命中率提高,但促销开始后出现价格展示不一致、热点商品回源和缓存集群内存耗尽。

改造首先区分数据:商品描述和图片可以使用边缘缓存,库存和实时价格需要更严格的版本和失效策略,用户个性化推荐按用户隔离。缓存键包含商品、区域、渠道和版本,避免不同场景互相污染。商品更新采用数据库提交后发布失效事件,消费端幂等删除;价格变化增加版本校验,旧值不能覆盖新值。

热点商品采用逻辑过期和互斥回源,提前预热并限制单键访问;缓存集群按业务分区和容量设置淘汰,超大对象拆分存储。缓存故障时,普通内容允许读取短时旧值,核心价格回源受限流保护,库存不依赖缓存作为唯一来源。监控命中率、回源、P99、热点、失效延迟和价格对账,确保性能改善没有牺牲业务正确性。

十、综合知识练习

题1

什么数据适合使用缓存?

参考答案:通常是读多写少、访问重复、计算或后端查询成本高、生命周期明确且允许一定陈旧的数据。强一致交易数据、敏感数据和高度个性化数据需要谨慎设计,不能因为访问快就直接缓存。

题2

缓存击穿、穿透和雪崩有什么区别?

参考答案:穿透是查询大量不存在的数据,缓存和数据库都被访问;击穿是单个热点键失效导致大量请求同时回源;雪崩是大量缓存同时失效或缓存整体故障,造成后端压力集中。对应措施分别包括空值/布隆过滤、互斥/逻辑过期和随机过期/多级缓存/限流降级。

题3

为什么缓存命中率高仍可能出现业务错误?

参考答案:命中数据可能过期、越权、版本落后或键设计错误。命中率只反映访问路径,不代表数据正确和业务一致,还要检查失效延迟、版本、权限、对账和核心业务结果。

十一、论文写作素材

论文中可以这样描述缓存演化:

“针对商品和内容查询造成数据库压力的问题,我按访问层次建立客户端、边缘、反向代理、应用本地和分布式缓存,依据读多写少、数据变化频率和一致性要求选择缓存对象。采用 Cache Aside 和失效事件维护数据,使用版本、幂等和补偿处理并发更新;通过空值缓存、互斥回源、随机过期、热点保护、限流和降级防止穿透、击穿和雪崩。对价格、库存和用户数据进行权限隔离和业务对账,持续监控命中率、回源率、长尾延迟、失效延迟和数据差异,确保性能提升不牺牲正确性。”

十二、今日复盘

  1. 缓存通过减少后端访问和重复计算改善性能,但会引入一致性和失效问题。
  2. 缓存层次包括客户端、边缘、代理、本地、分布式和数据库缓存。
  3. 缓存策略要结合数据变化、业务一致性、权限和生命周期。
  4. 穿透、击穿、雪崩、热点、大键和故障需要分别治理。
  5. 缓存不可作为核心持久化数据的唯一来源,必须有回源、对账和恢复路径。
  6. 案例答题要写清缓存对象、读写模式、一致性、故障保护和监控指标。