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

大家好,我是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数据为准

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

相关推荐
落落Plus11 小时前
浏览器缓存机制详解
javascript·缓存·浏览器缓存
瞬间&永恒~13 小时前
【MySQL】4-6:在同一个主机上使用 systemd 运行多个 MySQL服务器
服务器·数据库·mysql
心机之蛙qee13 小时前
Redis的主从、哨兵及集群
数据库·redis·缓存
qq_2975746715 小时前
RuoYi框架二次开发系列(五)性能优化、Redis缓存实战、分布式部署、接口限流与安全加固生产方案
redis·缓存·性能优化
一嘴一个橘子15 小时前
MysqL 哪些情况适合创建索引
mysql
时间的拾荒人15 小时前
MySQL C语言连接 - 从入门到实战
android·c语言·mysql
Database_Cool_17 小时前
云数据库控制台好不好用、能不能可视化操作:阿里云 RDS MySQL 控制台体验详解
数据库·mysql·阿里云
番茄炒鸡蛋加糖18 小时前
MySQL 实战调优& 分表基础
数据库·mysql
万亿少女的梦16818 小时前
基于Spring Boot的游戏交易管理系统设计与实现
java·spring boot·mysql·系统设计·交易管理
Database_Cool_18 小时前
云数据库如何保证高可用、故障了怎么办:阿里云 RDS MySQL 高可用架构详解
数据库·mysql·阿里云