redis灵魂拷问系列之如何保证数据库和缓存双写一致性
1.什么是数据库缓存双写一致性呢?
一般为了加快查询响应速度等,我们通过加缓存来提高接口的QPS,例如redis缓存、JVM缓存等。
数据是 缓存和数据库共享的。在 高并发场景下更新数据 时,因为数据库和缓存都要更新,就很可能会有数据一致性问题,其实也是分布式场景下数据一致性的问题
什么时候会发生数据库缓存双写不一致的情况呢?
例如:高并发场景下,先更新数据库,再删除缓存之后,另外一个线程把旧数据存入缓存
2.选择更新缓存还是删除缓存?
这里就需要提到Cache Aside Pattern,也是我们日常编码中一般会采取的策略
- 读取数据时,先读缓存,缓存不存在再读数据库,然后把结果存入缓存
- 更新数据时,先更新数据库,再删除缓存
为什么是删除缓存不是更新缓存呢?
假如为 更新缓存
- 如果数据更新频繁,则每一次都需要更新缓存,如果加上还是联表查询,则更新缓存的代价就很大
- 如果数据不会被频繁查询到(冷数据),这时候频繁更新缓存则是在浪费资源(例如数据A一分钟内被更新20次,但是只查询了一次)
- 还可能存在更新缓存失败的情况
假如为 删除缓存
- 数据在删除后,第一次被访问时,会从数据库读取(类似懒加载方式),保证了只有被用到才去操作,减少了资源浪费
所以当之无愧,选择 删除缓存
3.先更新数据库,还是先删除缓存?
- 先删除缓存,删除成功后去更新数据库。会出现问题:高并发场景下,删除成功后,可能另外一个请求查询后又把旧数据存入缓存中。这时候要么采用 延迟双删,也就是睡眠一定时间后再次删除一次,要么选择下一种做法
- 先更新数据库,再删除缓存。会出现问题:高并发下,在删除成功后,可能其他线程把旧数据再存入缓存中的问题。也会出现数据不一致的情况(影响相对较小所以可以接受)
4.具体落地的解决策略有哪些?
(1)延时双删策略
- a. 删除缓存
- b. 更新数据库
- c. 睡眠一段时间,具体多长有业务决定(mysql读写分离的话还需要加上主从同步时间)
- d. 再删除缓存(吞吐量降低的话使用 异步)
(2)更新数据库、后删缓存
(3)串行化思维保证两者的最终一致性
利用binlog天然的顺序性用来做同步操作,顺序删除缓存,删除缓存失败则使用消息队列的同步性重试删除缓存
(4)强一致性
如果需要保证强一致性,那么是否还有添加缓存的必要(没有海量qps的情况下)?
可以参考这篇文章:缓存与数据库一致性系列-04 - Kido的博客 | Kido’s Blog
【参考链接】
1:Redis与Mysql双写一致性方案解析
2:缓存与数据库一致性系列-03
3:缓存与数据库一致性系列-04