设计缓存方案时需关注的问题及解决方案
缓存更新策略
Cache Aside
Query
- 应用程序先从Cache取数据
- 未命中:从DB中取数据,成功后更新到Cache
- 命中:应用程序从cache中取数据,取到后返回
Update
- 把数据更新到DB中,成功后,让Cache失效
为什么删缓存,而不是更新缓存
- 性能:删除 比 更新 快,在写多的场景下,减少浪费。
- 正确性:在并发场景下,在写请求中更新缓存可能会引发数据的不一致问题。线程1比线程2早到,但线程1在线程2之后更新。导致缓存中存的是线程1的旧数据。线程3读到的就是旧数据。
为什么先更新数据库,而不是先删缓存
防止缓存穿透:先删缓存,请求都打到DB,增大缓存穿透概率。
正确性:
- 并发场景下,线程1写数据,先删缓存。
- 线程2读数据,缓存不存在,读DB。读完DB更新缓存。
- 线程1删完缓存,更新DB。
此时缓存中是旧数据。线程3读到的是旧数据。
cache-aside存在的问题
并发问题:
- 线程1查询未命中缓存,到DB取数据。
- 线程2更新操作,更新了DB数据,并删除缓存
- 线程1更新缓存(3.1)晚于线程2删除(2.2),将旧数据放到了缓存中。
- 线程3读到的是旧数据。
解决办法
- 分析出现概率:低
- 出现的条件:
- 读缓存时 缓存失效,而且有并发写操作。
- 实际场景,写比读慢得多,还需要锁表。需要满足 读在写操作之前结束又晚于写。
所有条件都具备的概率不大,通过设置合理的过期时间降低问题的影响程度。
即,即使缓存出现了脏数据,使其影响范围有限。
Read/Write Through
更新数据库的操作由缓存代理,在应用层看来,认为后端就是一个单一的存储,这个存储维护自身的Cache
Query
- 命中:直接返回数据
- 未命中:由缓存服务从DB取数据更新到Cache,再返回给应用方。
Update
- 命中:先更新Cache,再更新DB
- 未命中:更新DB后直接返回。
Write Behind Caching
- 更新数据时,只更新缓存,不更新数据库
- 缓存异步、批量更新数据库
Cache更新失败的应对策略
- 记录日志,补偿保障最终一致性
- 异步更新,保障最终一致性
- 设计合理的Cache过期时间
设计缓存时的注意事项
如何有效缓存数据
- 适合缓存的数据
- 很少变化的数据
- 在应用程序运行时避免重复计算
- 缓存的时间
- 第一次由应用程序检索时添加到缓存
- 事先在缓存中部分或完全填充数据
管理缓存中的数据过期
- 为数据设置过期时间,过期时间为绝对值
- 不同对象设置不同过期时间
缓存填满处理策略
- 数据淘汰
- 最近最少使用原则
- 最新使用的策略(淘汰刚被用过的数据)
- 先进先出(淘汰最近的数据)
- 根据业务上已设置的触发条件淘汰
- 不淘汰,直接报错
常见问题与防范
Cache Penetration(缓存穿透)
查询一条数据库不存在的数据,也就是缓存和数据库都查询不到这条数据,这样请求每次都会穿透到数据库
解决方法
- 缓存空值,注意 设置过期时间
- Bloom Filter,判断一个元素是否存在,不存在直接返回。
Cache BreakDown(缓存击穿)
在高并发的系统中,大量的请求同时查询一个 key时,当空上key刚好失效,会导致大量的请求访问数据库,这种现象我们称为缓存击穿。
解决方法
- 并发查询时加锁
- 提前异步更新cache,使cache不失效
缓存雪崩
某一时刻发生大规模的缓存失效,包括缓存服务宕机,缓存集体过期等,导致大量的请求进来访问数据库,导致雪崩
解决办法
- 过期时间有一定的随机性
- 对DB做熔断、降级、限流
热点数据集中失效
缓存数据使用相同的过期时间,如果在同一时刻同时多个热点数据过期,可能导致对数据库的访问急剧升高。
解决办法
- 失效时间有一定的随机性
- 互斥锁
- 需要查DB的请求先获取锁,只有获取到锁的才能查询。查询成功后更新缓存,并释放锁
- 获取到锁的线程 判断cache是否已经有数据,有数据直接返回,没有数据再请求DB
- 注意:这种方式会阻塞并发查询,系统吞吐量下降,需要结合实际业务场景考虑使用
缓存脏数据
指更新后的缓存数据与数据库数据不一致,导致影响业务。
解决办法
- 防御性编程:缓存更新成功后,检查cache与DB是否一致
- 限制缓存写入点
- 建立缓存数据正确性监控/告警机制
- 设置缓存失效开关。注意 需要同时配备防缓存雪崩、缓存快速重建机制
缓存的监控
- 使用的内存量
- 总读取次数
- 总写入次数
- 总命中次数
- 读失败数
- 写失败数
- 单Key读次数
- 单Key写次数
- 安全性相关监控
缓存失效容忍性演练
定期演练:
- 具备真实流量压测能力
- 模拟缓存雪崩场景,例如使用缓存功能开关,关闭缓存
- 验证熔断机制
- 验证限流机制
- 验证降级机制
- 验证DB可用性