缓存

设计缓存方案时需关注的问题及解决方案

缓存更新策略

Cache Aside

Query

  1. 应用程序先从Cache取数据
  2. 未命中:从DB中取数据,成功后更新到Cache
  3. 命中:应用程序从cache中取数据,取到后返回

Update

  • 把数据更新到DB中,成功后,让Cache失效

为什么删缓存,而不是更新缓存

  1. 性能:删除 比 更新 快,在写多的场景下,减少浪费。
  2. 正确性:在并发场景下,在写请求中更新缓存可能会引发数据的不一致问题。线程1比线程2早到,但线程1在线程2之后更新。导致缓存中存的是线程1的旧数据。线程3读到的就是旧数据。

image-20211229154930217

为什么先更新数据库,而不是先删缓存

  1. 防止缓存穿透:先删缓存,请求都打到DB,增大缓存穿透概率。

  2. 正确性:

    • 并发场景下,线程1写数据,先删缓存。
    • 线程2读数据,缓存不存在,读DB。读完DB更新缓存。
    • 线程1删完缓存,更新DB。
    • 此时缓存中是旧数据。线程3读到的是旧数据。

      image-20211229155618523

cache-aside存在的问题

并发问题:

  1. 线程1查询未命中缓存,到DB取数据。
  2. 线程2更新操作,更新了DB数据,并删除缓存
  3. 线程1更新缓存(3.1)晚于线程2删除(2.2),将旧数据放到了缓存中。
  4. 线程3读到的是旧数据。

image-20211229161832900

解决办法

  • 分析出现概率:低
  • 出现的条件:
    1. 读缓存时 缓存失效,而且有并发写操作。
    2. 实际场景,写比读慢得多,还需要锁表。需要满足 读在写操作之前结束又晚于写。

所有条件都具备的概率不大,通过设置合理的过期时间降低问题的影响程度。
即,即使缓存出现了脏数据,使其影响范围有限。

Read/Write Through

更新数据库的操作由缓存代理,在应用层看来,认为后端就是一个单一的存储,这个存储维护自身的Cache

Query

  • 命中:直接返回数据
  • 未命中:由缓存服务从DB取数据更新到Cache,再返回给应用方。

Update

  • 命中:先更新Cache,再更新DB
  • 未命中:更新DB后直接返回。

Write Behind Caching

wecom-temp-50f0656639bd0f98f5655924bfffd3bc

  • 更新数据时,只更新缓存,不更新数据库
  • 缓存异步、批量更新数据库

Cache更新失败的应对策略

  • 记录日志,补偿保障最终一致性
  • 异步更新,保障最终一致性
  • 设计合理的Cache过期时间

设计缓存时的注意事项

如何有效缓存数据

  • 适合缓存的数据
    • 很少变化的数据
    • 在应用程序运行时避免重复计算
  • 缓存的时间
    • 第一次由应用程序检索时添加到缓存
    • 事先在缓存中部分或完全填充数据

管理缓存中的数据过期

  • 为数据设置过期时间,过期时间为绝对值
  • 不同对象设置不同过期时间

缓存填满处理策略

  • 数据淘汰
    • 最近最少使用原则
    • 最新使用的策略(淘汰刚被用过的数据)
    • 先进先出(淘汰最近的数据)
    • 根据业务上已设置的触发条件淘汰
  • 不淘汰,直接报错

常见问题与防范

Cache Penetration(缓存穿透)

查询一条数据库不存在的数据,也就是缓存和数据库都查询不到这条数据,这样请求每次都会穿透到数据库

解决方法

  • 缓存空值,注意 设置过期时间
  • Bloom Filter,判断一个元素是否存在,不存在直接返回。

Cache BreakDown(缓存击穿)

在高并发的系统中,大量的请求同时查询一个 key时,当空上key刚好失效,会导致大量的请求访问数据库,这种现象我们称为缓存击穿。

解决方法

  • 并发查询时加锁
  • 提前异步更新cache,使cache不失效

缓存雪崩

某一时刻发生大规模的缓存失效,包括缓存服务宕机,缓存集体过期等,导致大量的请求进来访问数据库,导致雪崩

解决办法

  • 过期时间有一定的随机性
  • 对DB做熔断、降级、限流

热点数据集中失效

缓存数据使用相同的过期时间,如果在同一时刻同时多个热点数据过期,可能导致对数据库的访问急剧升高。

解决办法

  • 失效时间有一定的随机性
  • 互斥锁
    • 需要查DB的请求先获取锁,只有获取到锁的才能查询。查询成功后更新缓存,并释放锁
    • 获取到锁的线程 判断cache是否已经有数据,有数据直接返回,没有数据再请求DB
    • 注意:这种方式会阻塞并发查询,系统吞吐量下降,需要结合实际业务场景考虑使用

缓存脏数据

指更新后的缓存数据与数据库数据不一致,导致影响业务。

解决办法

  • 防御性编程:缓存更新成功后,检查cache与DB是否一致
  • 限制缓存写入点
  • 建立缓存数据正确性监控/告警机制
  • 设置缓存失效开关。注意 需要同时配备防缓存雪崩、缓存快速重建机制

缓存的监控

  • 使用的内存量
  • 总读取次数
  • 总写入次数
  • 总命中次数
  • 读失败数
  • 写失败数
  • 单Key读次数
  • 单Key写次数
  • 安全性相关监控

缓存失效容忍性演练

定期演练:

  1. 具备真实流量压测能力
  2. 模拟缓存雪崩场景,例如使用缓存功能开关,关闭缓存
  3. 验证熔断机制
  4. 验证限流机制
  5. 验证降级机制
  6. 验证DB可用性
于大帅 wechat
打钱! 打钱! 打钱😡😡😡
打赏完了让我夸夸你🤨