架构师必备:缓存更新模式总结

大家好,我是Java烘焙师。如何更新缓存和DB、做到性能和一致性的取舍,是一个很常见的话题。下面结合笔者的经验和思考,系统性地总结一下缓存更新模式,讲透讲明白。

1、旁路缓存(cache-aside)

实现方案

  • 查询:先查缓存,查不到缓存时再查DB,并把DB内容写入缓存、设置合适的过期时间
  • 更新:先更新DB,再删缓存;做到极致则需引入延迟双删机制

之所以不是先删缓存、再更新DB,是因为在这两个操作间隙,如果有其它查询请求,则会把DB旧值写到缓存。

之所以不是先更新DB、再更新缓存,是因为写DB和缓存无法保证一致性,并且可能因为2个并发写的时序问题而把旧数据写到缓存。

之所以延迟双删,是因为在极端情况下,读线程会把DB旧值写到缓存。需要同时满足几个条件:缓存已过期,并且读线程先查询到DB旧值,然后写线程更新DB、删除缓存之后,读线程才把DB旧值写入缓存。如下图所示。

因此第一次删除缓存后,延迟一小段时间再删除,就能保证缓存和DB的最终一致。下图是引入了延迟双删机制的cache-aside架构图。

cache-aside查询场景:

cache-aside更新场景:

适用场景

  • 绝大部分场景

优点

  • 当数据量大时,可按需加载到缓存

缺点

  • 如果存在热点key,在失效后,会有大量查询请求穿透缓存,直接打到DB,造成DB CPU使用率飙升

旁路缓存优化:主动预刷新缓存

为了解决热点缓存失效问题,可考虑设置TTL为较长时间,并主动预刷新热点key。

根据数据量大小区分:

  • 如果数据量较大,则针对热点key,配置白名单。做得更好的话,是自动发现、并更新热点key白名单。
  • 如果数据量较小,则可以考虑全部加载到缓存中,永不过期。如:一些全局的配置数据。

根据触发刷新缓存的时机区分:

  • 定时拉取:程序自行实现,根据热点key白名单,定时查DB、并更新缓存
  • 异构数据:监听mysql变更,DB变化时触发更新缓存

更推荐异构数据的方式,好处是:缓存更新及时,并且做成通用功能之后、无需额外开发。

2、异步写回DB模式(write-back)

实现方案

  • 查询:只查询缓存
  • 更新:先写入缓存,然后发消息、消息链路异步写入DB,或定时任务兜底写入DB

适用场景

查询qps很高、极其热点的数据,优先保证性能。

场景举例:

  • 计数统计:有的页面会滚动刷新访问人次、使用人次
  • 爆品库存扣减:redis扣减库存,然后异步落库,而不是常规地操作DB扣减库存

优点

  • 支撑高qps、热点场景

缺点

  • 短期内会出现缓存和DB数据不一致情况,需要消息触发、或兜底定时任务写回DB

3、read/write through模式

实现方案

不论是cache-aside、还是write-back模式,都需要应用程序自己来控制读写缓存、DB。而read/write through模式是把控制权交给底层存储服务。

存储服务维护缓存、持久化数据,应用程序无需感知,这也是优点了。不过完全依赖于存储服务是否靠谱,实际业务场景并不常见。

4、持续优化

搭积木方式,根据实际情况做优化。

多级缓存:进一步降低缓存、DB的热点风险

  • 增加本地缓存,如caffeine
  • 或增加DB以外的异构数据,当查不到缓存时再查异构数据、查不到异构数据时最终查DB。异构数据可以是HBase、ES等

通过逻辑层面来实现生效、过期的效果,而非系统层面

  • 架构设计必须适配业务,比如通过逻辑过期解决不一致、缓存集中过期的问题,如缓存记录业务开始时间、结束时间,TTL可设置稍长些、并且通过增加随机时长来避免key集中失效。这样就能实现到时间点就变的场景,如活动开始、结束。

强一致场景,只查DB、已DB数据为准

  • 特别地,对一致性有强要求的场景:只查DB、不查缓存,以DB数据为准。如下单时查询DB里的价格,避免缓存数据非最新。

更进一步,考虑使用rocksdb,代替redis

  • rocksdb相当于是自带缓存的持久化数据库,值得专门写一篇文章介绍原理、区别,后面有空整理。

结论

  • 绝大部分场景,使用旁路缓存模式(cache-aside)。更进一步,对部分热点key做主动预刷新,可监听DB变更、或定时刷新。
  • 高qps、极热key场景,使用异步写回DB模式(write-back),优先保证性能,可接受短时间内DB与缓存不一致。
  • 持续优化:
    • 增加多级缓存、异构数据,来降低缓存、DB的热点风险
    • 通过逻辑层面来实现生效、过期的效果
    • 强一致场景,只查DB、已DB数据为准

延伸阅读:笔者之前写的缓存相关文章,欢迎围观。

相关推荐
靡樊3 小时前
MySQL:C语言链接
数据库·mysql
一只游鱼3 小时前
linux使用yum安装数据库
linux·mysql·adb
熏鱼的小迷弟Liu5 小时前
【MySQL】一篇讲透MySQL的MVCC机制!
数据库·mysql
鹅是开哥7 小时前
Redis的零食盒满了怎么办?详解缓存淘汰策略
java·redis·缓存·bootstrap
一匹电信狗8 小时前
【MySQL】数据库基础
linux·运维·服务器·数据库·mysql·ubuntu·小程序
奥尔特星云大使12 小时前
MySQL 备份基础(一)
数据库·sql·mysql·备份·mysql备份
努力学习的小廉13 小时前
初识MYSQL —— 库和表的操作
数据库·mysql·oracle
代码不停14 小时前
计算机工作原理(简单介绍)
数据库·redis·缓存
简色16 小时前
预约优化方案全链路优化实践
java·spring boot·后端·mysql·spring·rabbitmq
小森( ﹡ˆoˆ﹡ )17 小时前
GPT_Data_Processing_Tutorial
数据库·gpt·mysql