2026.3.12 常见的缓存读写策略

2026.3.12 常见的缓存读写策略

Cache Aside Pattern(旁路缓存模式)

这是我们日常开发中最常用、最经典的一种模式,几乎是互联网应用缓存方案的事实标准,尤其适合读多写少的业务场景。

这个模式之所以被称为"旁路"(Aside),是因为应用程序的写操作完全绕过了缓存,直接操作数据库

下面我们来看一下这个策略模式下的缓存读写步骤。

写操作:

  1. 应用先更新DB

  2. 然后直接删除Cache中对应的数据

读操作:

  1. 应用先从Cache读取数据

  2. 如果命中(Hit),则返回

  3. 如果未命中(Miss),则从DB读取数据,成功读取后,将数据写回Cache,然后返回。

1.为什么写操作是"先更新DB,后删除Cache",顺序能反过来吗?

绝对不能。如果这样做,在高并发的情况下会引入经典的数据不一致问题。

  • 时序分析(请求A写,请求B读)

    1. 请求A:先将Cache中的数据删除

    2. 请求B:此时发现Cache中为空,则去DB中读旧数据,准备写入Cache

    3. 请求A:将新值写入DB

    4. 请求B:将之前读到的旧值写入Cache

  • 结果:DB中是新值,Cache中是旧值,数据不一致。

2.那"先更新DB,后删除Cache中的旧值"一定安全吗?

也不是绝对安全。因为这样也可能会造成数据库和缓存数据不一致的问题。

  • 时序分析(请求A写,请求B读):

    1. 请求B:发现Cache中为空,去DB中读旧数据,准备写入Cache

    2. 请求A:迅速地更新完了DB中的旧数据,并执行删除Cache就数据操作

    3. 请求B:慢悠悠地把旧数据写入Cache

  • 结果:DB中最新值,而Cache中仍是旧值

  • 为什么概率极小?这个问题本质上是一个并发时序问题:只要"读DB->写Cache"这段时间窗口内,恰好有写请求完成了DB更新,就有可能产生数据不一致。在大多数业务里,这个窗口时间相对较短,而且还需要与写请求并发"撞车",所以发生概率不算高,但也不是绝无可能。

3.为什么是"删除Cache",而不是"更新Cache"?

  • 性能开销:写操作往往只更新对象的部分字段,如果为了"更新Cache"而去重新查询或计算整个缓存对象,开销可能很大。相比之下,"删除"是一个极轻量的操作。

  • 懒加载思想:"删除"操作遵循懒加载原则。只有当数据下一次被真正需要时(读操作),才触发从DB加载并写入缓存,避免了无效的缓存更新。

  • 并发安全:"更新缓存"在高并发的情况下可能出现顺序错乱的问题导致脏数据的概率会更大。

当然,这一切都建立在一个重要的前提之上:我们缓存的数据,是可以通过数据库进行确定性重建的,并且业务上可以容忍从"缓存删除"到"下一次读取并回填"之间这个极短时间窗口内的数据不一致。

现在让我们再分析一下Cache Aside Pattern的缺陷。

缺陷

  1. 首次请求数据一定不在Cache的问题 解决方法:对于访问量巨大的热点数据,可以在系统启动或低峰期进行缓存预热。

  2. 写操作比较频繁的话导致Cache中的数据被频繁删除,进而影响缓存命中率 解决方法:

    • 数据库和缓存数据强一致场景:更新DB的时候同时更新Cache,不过我们需要加一个分布式锁来保证更新Cache的时候不存在线程安全的问题。

    • 可以短暂地允许数据库和缓存数据不一致的场景:更新DB的时候同时更新Cache,但是给缓存加一个比较短的过期时间(如1分钟),这样的话就可以保证即使数据不一致的话影响也会降到最低。

Read/Write Through Pattern(读写穿透)

在这种模式下,应用程序将Cache视为唯一的、主要的存储。所有的读写请求都直接打向Cache,而Cache服务自身负责与DB进行数据同步。

对应用程序透明,应用开发者无需关心DB的存在。

这种缓存读写策略再平时开发过程中非常少见。抛去性能方面的影响,大概率是因为我们经常使用的分布式缓存Redis本身并没有提供Cache将数据写入DB的功能,需要我们在业务侧或中间件里自己实现。

写(Write Through):

  • 先查Cache,Cache中不存在,直接更新DB

  • Cache中存在,则先更新Cache,然后Cache服务自己更新DB。只有当Cache和DB都写入成功后,才向上层返回成功。

读(Read Through):

  • 应用从Cache中读取数据

  • 如果命中,直接返回

  • 如果未命中,由Cache服务自己负责从DB中读取数据,加载成功后先写入自身,再返回给应用

Read-Through实际只是再Cache-Aside上进行封装。在Cache-Aside下,发生读请求的时候,如果Cache中不存在对应的数据,是由客户端自己负责把数据写入Cache,而Read Through是由Cache服务自己写入缓存,而这一切对客户端是透明的。

和Cache Aside一样,Read-Through 也存在首次请求数据的时候缓存未命中的问题,对于热点数据可提前放入缓存中。

Write Behind Pattern(异步缓存写入)

Write Behing(也被称为Write-Back)Pattern和Read/Write Through Pattern很相似,两者都是由Cache服务来负责Cache和DB的读写。

但是,两者又有很大的不同:读写穿透是同步更新Cache和DB,而异步缓存写入则是只更新缓存,不直接更新DB,而是改为异步批量的方式来更新DB

写操作(Write Behind):

  1. 应用将数据写入Cache,然后立即返回。

  2. Cache服务将这个写操作放入队列

  3. 通过一个独立的异步线程,将队列中的写操作批量地、合并地写入DB。

这种模式对数据一致性带来了挑战(例如:Cache中的数据还没来得及写回DB,系统就宕机了),因此不适用于需要强一致性的场景(如交易、库存)。

但是,它的异步和批量特性,带来了无与伦比的写性能。它在许多高性能系统中都有广泛的应用:

  • MySQL的InnoDB Buffer Pool机制:数据修改先在内存Buffer Pool中完成,然后由后台线程独立异步地刷写到磁盘中。

  • 操作系统的页缓存(Page Cache):文件写入也是先写到内存,再有操作系统异步刷盘

  • 高频计数场景:对于文章浏览量、帖子点赞数这类数据允许短暂的数据不一致、但写入极其频繁的场景,可以先在Redis中快速累加,再通过定时任务异步同步回数据库。

相关推荐
双叶8362 小时前
(Python)Python爬虫入门教程:从零开始学习网页抓取(爬虫教学)(Python教学)
后端·爬虫·python·学习
ruanyongjing2 小时前
Spring TransactionTemplate 深入解析与高级用法
java·数据库·spring
fengxin_rou2 小时前
[Redis从零到精通|第六篇]:Redis的主从同步
java·数据库·redis·缓存
zhglhy2 小时前
Java系统限流方法技术优劣
java·限流
xiaoye37082 小时前
Spring Bean 生命周期
java·spring
6+h2 小时前
【Spring】Bean的生命周期详解
java·python·spring
冬夜戏雪2 小时前
面经摘录(五)
java·后端·spring
李昊哲小课2 小时前
Python CSV 模块完整教程
java·服务器·python
人道领域2 小时前
苍穹外卖:菜品分页查询与删除功能(保姆级详解)
java·开发语言·数据库·后端·spring