系统设计专题01:缓存策略

Posted by LiYixian on Monday, February 9, 2026 | 阅读 | ,阅读约 4 分钟

什么是缓存?

  • 把数据存在比 original source 访问更快的地方
  • 存什么,什么时候存,什么时候失效
  • 热点 key 的高可用能让系统更快地响应用户
  • 提高整体的性能和用户体验
  • 也提升了可扩展性和可用性

Cache-aside (Lazy Loading)

Use cases: 大部分 CRUD 场景,读多写少

  • 读缓存
  • Cache miss 时读 DB
  • 更新缓存
  • 写的时候先更新 DB 再删除缓存(注意不是更新,否则并发很容易错)

Pros:

  • 实现简单、侵入性小,和现有数据库模型耦合低,失败时最多回退到查 DB,不会把系统搞炸
  • 缓存存储的都是所需要的数据

Cons:

  • First/misses request is slow
    • 并发情况下读写之间存在短暂不一致窗口,不是强一致
  • 在高并发下容易出现缓存击穿(同一个 key 失效瞬间,大量请求打到 DB)
  • 需要自己处理好删除缓存的时机

Read-Through

Use cases: Stable data and high read

  • 读缓存
  • Cache miss 时,缓存自己读 DB

Pros:

  • 应用的逻辑很简单,易于 scale 读操作
  • 缓存和存储的一致性由缓存层保证,对业务代码友好

Cons:

  • 缓存需要额外的操作(通常意味着中间件或自研层)
  • 缓存和 DB 强耦合,缓存一旦出问题,影响很大

Write-Through

Use cases: 读一致性要求高 (e.g., shopping cart)

  • 写缓存
  • 缓存写 DB

Pros:

  • 缓存的数据总是最新的
  • No stale reads
  • 读延迟很低

Cons:

  • 写延迟高,缓存没有加速写
  • 缓存会成为写瓶颈(因为和数据库强耦合)
  • 不常访问的数据也会存在缓存里

Write-Back (Behind)

Use cases: 高吞吐

  • 应用持续写缓存
  • 缓存异步写 DB

Pros:

  • 写性能极高,可以合并写、削峰填谷,对 DB 很友好

Cons:

  • 缓存和 DB 达到的是最终一致性
  • 如果缓存宕机了,数据就可能丢失
  • 不频繁访问的数据也存在缓存里
  • 实现复杂,需要 WAL、重试、顺序保证等机制

Time-to-live (Expiration)

Use cases: 定期修改的缓存数据 (e.g., session data)

和 cache aside 差不多,除了过期的缓存数据会被移除之外

Pros:

  • 自动清理,减少了 outdated 数据

Cons:

  • 如果大量 key 同时过期,会缓存雪崩
  • 缓存数据可能会在过期之后又被访问,造成 cache miss

Refresh Ahead

Use cases: 稳定的 hot key 和访问模式 (首页配置、榜单、热点内容)

设置 TTL,但在即将过期前,后台线程或异步任务提前刷新缓存(或者每次访问时都刷新),而不是等用户请求触发 miss,这样 hot key 实际上永不过期(逻辑过期)。

Pros:

  • 显著降低缓存击穿概率
  • 用户请求几乎永远命中 cache
  • 延迟稳定

Cons:

  • 可能会刷新一些不再需要的 key
  • 一致性是最终一致,用户可能会读到旧数据
  • 实现复杂,需要后台任务、调度

本地缓存 + 分布式缓存

Use cases: 高 QPS,需要低延迟 (e.g., recommendation, ads)

进程内缓存(如 Caffeine / Guava) → Redis → DB

Pros:

  • 读延迟很低
  • 大幅降低 Redis 压力

Cons:

  • Consistency hazard
    • 异步失效通知、版本号 / 短 TTL
  • 本地缓存失效、容量、垃圾回收都要处理

Cache Problems

Cache Avalanche 缓存雪崩

大量 key 在同一时间失效。

原因:

  • 同一批 key 设置了相同 TTL
  • Redis 宕机
  • cache service 抖动 / unavailable

Solution:

  • Random TTL 打散失效时间
  • 多级缓存
  • 熔断、限流、降级
  • 通过主从节点构建 Redis 高可靠 cluster

Hotspot Invalid 缓存击穿

Cache 中某个 hot key 过期时,大量请求访问该数据,全部 cache miss 后访问 DB,DB 容易被高并发请求冲垮。

Solution:

  • 互斥锁 singleflight,cache miss 时只有一个业务线程去查 DB ,然后更新缓存;其他线程等待锁释放后再读缓存
    • 一般要给锁加上 TTL,防止出现故障没有线程释放锁
  • 前面提到的 fresh ahead 逻辑过期:不给 hot key 设置过期时间,后台异步更新缓存,或者在快要过期时提前通知后台线程更新缓存,重新设置 TTL
    • 一致性是最终一致,更新还没完成这期间的查询都会返回旧数据

Cache Penetration 缓存穿透

高频请求的数据在 cache 和 DB 里都不存在。(请求 userId = -1,请求不存在的订单号,恶意扫描 ID 空间)

Solution:

  • 限制非法请求,在 API 入口校验请求参数
  • 缓存空值,在 DB 查到不存在的值时也缓存 value = null
    • 如果攻击者不断换 key(比如扫 ID),缓存会被塞满大量空值,所以一般要配合容量限制、TTL 很短
  • Bloom Filter:快速判断这个 key 是否有可能存在
    • 可能有 false positives,但 NO false negatives
    • 有维护成本,不适合频繁变更的数据,实现复杂度比缓存空值高;一般在 ID 空间大、攻击风险高时使用

Bloom Filter

It uses a bit array and multiple hash functions to enable fast lookups (constant time) with minimal memory. Cache 的前置防护,减少 DB 压力

A Bloom filter with \(m\) bits and \(k\) hash functions is initialized to all zeros. To add an item \(x\), \(k\) hash functions \(h_{1}(x),\dots,h_{k}(x)\) map it to \(k\) array positions, which are then set to 1.

Amazon Use Case

Cache Aside

  • Product detail pages
  • Search autocomplete
  • Personalization recommendations

Read Through

  • AWS API Gateway caching 缓存下游服务的响应
  • Internal service frameworks that wrap data access with caching

Write Through

  • Shopping cart service
  • Inventory availability service 保证缓存里的库存和数据库保持同步,减少库存超卖风险

Write Back

  • User activity counters 点赞、播放数、浏览量
  • Metrics aggregation / Analytics events
  • Recommendation feature updates

TTL

  • Session tokens
  • Search results 可能变化较快
  • API Gateway
  • Rate limiting counters
  • Featured flag / A-B test config