当前位置: 首页 > news >正文

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

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

1、旁路缓存(cache-aside)

实现方案

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

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

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

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

image

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

cache-aside查询场景:
cache-aside查询场景

cache-aside更新场景:
cache-aside更新场景

适用场景

  • 绝大部分场景

优点

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

缺点

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

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

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

根据数据量大小区分:

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

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

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

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

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

实现方案

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

write-back模式

适用场景

查询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数据为准

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

  • 架构师必备:本地缓存原理和应用
  • Spring cache源码分析
http://www.wxhsa.cn/company.asp?id=4369

相关文章:

  • 为什么不能在try-catch中捕获子线程的异常 ?
  • sensitive-word 敏感词性能提升14倍优化全过程 v0.28.0 - 实践
  • 2025 PHP 开发者必看得 25 个容易犯的常见错误 90% 的开发者都踩过
  • 一款带有AI功能的markdown工具
  • 45万亿!中国智驾的新风口来了
  • apache poi 导出繁琐的excel表格
  • Ubuntu Server SSH 连接
  • 利用竞态条件轻松上传Web Shell
  • 我亲眼目睹我上海的家长朋友陷进去了
  • 蔚小理的辅助驾驶,谁最拉跨?
  • C 语言的 printf() 函数
  • 【GitHub每日速递 250915】3 个宝藏开源项目:超长语音合成、算法学习库、自托管软件导航,开发者速收
  • C 语言头文件
  • AFL++环境搭建
  • 晚安
  • 读人形机器人12体育领域
  • 【QT】C++基础
  • 安全研究者的MCP服务器宝典:BugBounty工具集锦
  • Unity的VisualStudio工程链接不同步、显示异常处理方法
  • Java 高性能与可维护性实战:从语言特性到工程化全链路
  • 二叉树的递归遍历
  • 我的大学成长与规划
  • 【笔记】拉格朗日插值
  • 自定义渲染管线(Unity Cocos)
  • 这是一个测试
  • 文献阅读 | Survey of Hallucination in Natural Language Generation
  • 技术 | LLaMA Factory微调记录重修版
  • 支付中心的钱包类业务应该怎么设计
  • MySQL索引浅析
  • WF 2025 游记