缓存与数据库数据一致性问题

在用了redis缓存的系统中,正常情况下,一个读操作会先查缓存,如果在缓存中查到了,则直接返回,如果缓存中没有,则会查数据库,再将查到的数据写到redis中,然后返回。如下图:

这样的流程没有啥问题,但是当数据变更时,怎么把缓存变到最新,这就是缓存一致性要考虑的问题。

首先最容易想到的就是在更新数据库的时候同时更新缓存。这就涉及到先更新缓存还是先更新数据库的问题。其实无论是先操作哪一个都可能会出现缓存不一致问题。

首先就是两个操作不是原子性的。就可能出现一个成功一个失败的情况。先更新数据库,然后缓存更新失败,这样就会导致缓存中存在的是一个错误的旧值。先更新缓存,数据库更新失败时,这样读取到的缓存依然是不正确的。这还是没有并发的情况。

其次考虑在并发场景下,两个线程同时进行先更新缓存后更新数据库的操作:

先更新数据库后更新缓存会导致一样的问题:

然后还有一个发生概率比较低的并发场景,那就是读写并发。读的过程就是一开始说的先查缓存,缓存中有直接返回,没有则查数据库,查完再写到缓存然后返回。如果读写次序是:读线程先读缓存,缓存没有,查数据库值假如为10,这时候更新缓存和数据库的写操作执行了写数据库和缓存为20,然后读线程接着又更新缓存它从数据库查到的10。数据库中为20,缓存中为10,又导致了不一致,如图:

所以更新缓存就不太行。就考虑直接删除缓存和更新数据库,由于是直接删除缓存,所以上面写写并发情况出现的不一致问题也就不存在了。而且删缓存相比于更新缓存更简单,也更不容易出错。因为很多情况下缓存中的数据可能并不是一个简单的字符串,而是一个比较大的JSON串,更新缓存要从缓存中反序列化得到对象然后更新,然后又要序列化成JSON再写到redis。而删缓存直接了当删掉,就很简单不容易出错。

但是删缓存依然存在两个操作无法保证原子性的问题:

假如先更新数据库后删缓存,删缓存失败了,就会导致缓存是旧值,数据不一致。

如果先删除缓存后更新数据库,即使更新数据库失败了也不会有脏数据,没什么影响,只要重试一次就好了。但是这种方案会无形中放大前面说的读写并发时的问题,因为先删缓存会增大缓存miss的概率(这种缓存Miss还可能会导致缓存击穿,可以通过加锁来解决)。还是前面的例子,一个读线程从缓存中没有查到值,然后查数据库查到10,这时候恰好一个写线程删缓存更新数据库为20,然后读线程更新缓存为10。这就又导致了不一致。

这种情况就要延迟双删来解决了,写线程先删缓存,写数据库后,延迟一会再删一次,这样就能把最后读线程写到缓存的脏数据给删掉了。延迟一般1~2秒。

总结一下延迟双删,第一次删除的目的是解决两个操作无法保证原子而导致不一致问题,所以选择先删缓存再更新数据库。第二次删除的目的是为了降低第一次删除而导致放大读写并发导致数据不一致的概率。

第二次删除失败怎么办?

也会出现,但第二次删除本来就是补偿性的。在读写并发这个小概率不一致事件中再失败,概率就更低。

相关推荐
大猫子的技术日记11 小时前
[百题重刷]前缀和 + Hash 表:缓存思想, 消除重复计算
java·缓存·哈希算法
愤怒的山羊13 小时前
jetcache List 缓存, json 序列化 泛型解析成了 JsonObject 处理
缓存·json·list
树在风中摇曳13 小时前
带哨兵位的双向循环链表详解(含 C 代码)+ LeetCode138 深度解析 + 顺序表 vs 链表缓存机制对比(图解 CPU 层级)
c语言·链表·缓存
斯文~16 小时前
「玩透ESA」站点配置阿里云ESA全站加速+自定义规则缓存
阿里云·缓存·云计算·cdn·esa
S***t71416 小时前
Python装饰器实现缓存
缓存
天硕国产存储技术站17 小时前
3000次零失误验证,天硕工业级SSD筑牢国产SSD安全存储方案
缓存·固态硬盘·国产ssd
前端炒粉20 小时前
35.LRU 缓存
开发语言·javascript·数据结构·算法·缓存·js
努力发光的程序员1 天前
互联网大厂Java面试:从Spring Boot到微服务架构
spring boot·缓存·微服务·消息队列·rabbitmq·spring security·安全框架
zero13_小葵司1 天前
JavaScript性能优化系列(八)弱网环境体验优化 - 8.3 数据预加载与缓存:提前缓存关键数据
javascript·缓存·性能优化
CS_浮鱼1 天前
【Linux进阶】mmap实战:文件映射、进程通信与LRU缓存
linux·运维·c++·缓存