缓存读写更新策略
缓存读写更新策略
- 旁路缓存(Cache-Aside)
- 读写穿透(Read/Write-Through)
- 异步写回(Write-Back)
旁路缓存(Cache-Aside)
由缓存的调用者,在更新数据库的同时更新缓存。
读时:读缓存 –> miss后读DB –> 写缓存
写时:更新DB –> 删除缓存
旁路缓存又叫手动加载缓存,读取缓存、读取数据库和更新缓存的操作都在应用系统完成。
核心思想:应用程序代码直接与缓存和数据源进行交互。
优点:
- 实现简单
- 缓存中只包含实际请求数据
缺点:
- 需要手动管理
- 可能存在缓存不一致
场景:
- 读多写少
- 需要对缓存的加载和更新时间进行精细化控制
适合绝大多数场景,也是平时生产用的最多的。
我们再来分析一下 Cache Aside Pattern 的缺陷。
缺陷 1:首次请求数据一定不在 cache 的问题
解决办法:可以将热点数据可以提前放入 cache 中。
缺陷 2:写操作比较频繁的话导致 cache 中的数据会被频繁被删除,这样会影响缓存命中率。
解决办法:
- 数据库和缓存数据强一致场景:更新 DB 的时候同样更新 cache,不过我们需要加一个锁/分布式锁来保证更新 cache 的时候不存在线程安全问题。
- 可以短暂地允许数据库和缓存数据不一致的场景:更新 DB 的时候同样更新 cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。
读写穿透(Read/Write-Through)
缓存与数据库整合为一个服务,由服务来维护一致性。
调用者调用该服务,无需关心缓存一致性问题。
读时:从缓存系统读数据 –> 缓存系统:读缓存 ->miss则读DB –> 回写缓存
写时:将写入请求发给缓存系统 –> 缓存系统:miss则仅更新DB,hit则更新cache和DB
读写穿透一般是一起配合的。下面一起介绍:
读穿透
读穿透,是一种自动加载缓存的策略。
这种策略与旁路缓存的读取逻辑非常相似。只是旁路缓存是由应用程序从数据库获取数据并填充。而读穿透是由缓存系统从数据库获取数据并填充,就好像在Redis上面加了一层代理。在Redis和数据库之间插入了一个透明的加载过程,实现了关注点分离,应用程序只与缓存系统交互,由缓存系统管理 Redis 和数据库之间的数据同步。
优点:
- 自动加载,数据库不在Redis的时候,自动触发从数据库加载数据操作,简化了应用程序的逻辑
- 透明性,数据库不用关心数据是否在Redis中,读取数据的方式和直接从数据库读取数据一致
缺点:
- 每次写入都需要等待缓存和数据库两步操作都完成,性能相较于其他模式较低
- 并发加载,多个并发数据首次读取相同数据时,可能导致并发加载多次相同的数据(这点可以通过比如go的singlefilght避免惊群效应)
场景:
- 读取频繁、不经常写入
- 数据相对稳定,不经常变化。
写穿透
是一种同步直写策略。当应用程序写入数据的时候,数据会同步写入Redis和数据。
类似读穿透,写穿透把写入责任转移给缓存系统,由缓存抽象层来完成写Redis,更新数据库。
写穿透颠倒了旁路缓存填充缓存的顺序。
优点:
- 数据一致性,缓存和数据库的数据总是最新的
- 查询性能最优,应用程序写入完成后,缓存和数据库都是最新的
缺点:
- 延迟:写入操作需要等待数据库确认,引入了一定的延迟
- 高并发写入场景下,同步写入数据存储的过程可能成为瓶颈
- 实现比较复杂,需要搭配特定模块或者组件
场景:
- 强一致性要求
- 读写比较平衡
- 实时性要求比较高
典型的是金融场景,电商库存订单之类的
异步写回(Write-Back)
调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证最终一致。
读时:miss后查DB并回写
写时:只更新cache,异步更新DB
当应用程序写入数据时,数据先写入缓存系统,由后台异步任务把这些变更批量写入数据库。
与写穿透类似,但是应用程序只与缓存系统交互,不必等待数据库更新完成。
写时:
- 应用程序写入:应用程序需要写入数据时,把写入请求发送给缓存系统,缓存系统把数据写入Redis,确保应用程序可以快速完成写入操作
- 消息队列:缓存系统维护一个消息队列,将写入Redis的数据添加到消息队列中
- 批量写入数据库: 异步任务将队列中累计的数据变更操作批量写入数据
- 确认写入完成:缓存系统更新相应的状态,标记已写入的数据变更操作,向应用程序返回写入操作已经写入完成的消息
实现了异步批量写入,避免每次写入都同步更新底层数据,提高写入性能。
通过异步队列和定期批量写入机制保证数据的最终一致性。
优点:
- 提高写入性能
- 降低写入延迟
缺点:
- 数据一致性延迟(由于异步写入)
- 系统故障或者异常情况下,尚未写入的数据可能丢失
场景:
- 能容忍一定的数据一致性延迟,能容忍一定的数据丢失
- 写入密集型场景或者需要批量处理数据
典型像是用户点赞,物联网数据上报
小结
这三种模式的核心区别在于:
谁来负责维护缓存和数据库之间的数据同步,以及写入操作是同步的还是异步的。
参考链接
- http://elmer.icu/2024/11/20/%E7%BC%93%E5%AD%98%E8%AF%BB%E5%86%99%E7%AD%96%E7%95%A5/
- 缓存更新策略和相应的数据一致性问题和方案.md