当前位置:网站首页>如何保证缓存和数据库一致性
如何保证缓存和数据库一致性
2022-07-26 16:20:00 【涛歌依旧fly】
缓存和数据库的关联
但随着业务量的增长,你的项目请求量越来越大,这时如果每次都从数据库中读数据,那肯定会有性能问题。
这个阶段通常的做法是,引入缓存来提高读性能,但是具体应该怎么使用呢?
如图为我们发起请求的大概流程

具体流程:
- 写请求依旧只写数据库
- 读请求先读缓存,如果缓存不存在,则从数据库读取,并重建缓存
- 同时,写入缓存中的数据,都设置失效时间
这样一来,缓存中不经常访问的数据,随着时间的推移,都会逐渐「过期」淘汰掉,最终缓存中保留的,都是经常被访问的热数据,缓存利用率得以最大化。
数据一致性问题(非并发)
当数据发生更新时,我们不仅要操作数据库,还要一并操作缓存。具体操作就是,修改一条数据时,不仅要更新数据库,也要连带缓存一起更新。
但数据库和缓存都更新,又存在先后问题,那对应的方案就有 2 个:
- 先更新缓存,后更新数据库
- 先更新数据库,后更新缓存
先不考虑并发问题,正常情况下,无论谁先谁后,都可以让两者保持一致,但现在我们需要重点考虑异常情况。
因为操作分为两步,那么就很有可能存在第一步成功、第二步失败的情况发生。
先更新缓存,后更新数据库
如果缓存更新成功了,但数据库更新失败,那么此时缓存中是最新值,但数据库中是旧值。
虽然此时读请求可以命中缓存,拿到正确的值,但是,一旦缓存失效,就会从数据库中读取到旧值,重建缓存也是这个旧值。
这时用户会发现自己之前修改的数据又变回去了,对业务造成影响。
先更新数据库,后更新缓存
如果数据库更新成功了,但缓存更新失败,那么此时数据库中是最新值,缓存中是旧值。
之后的读请求读到的都是旧数据,只有当缓存失效后,才能从数据库中得到正确的值。
这时用户会发现,自己刚刚修改了数据,但却看不到变更,一段时间过后,数据才变更过来,对业务也会有影响。
保证第二步成功执行,就是解决问题的关键
失败后进行重试,直到成功,但为了避免占用太多资源,应该采用异步重试,其实就是把重试请求写到消息队列中,然后由专门的消费者来重试,直到成功。或者更直接的做法,为了避免第二步执行失败,我们可以把操作缓存这一步,直接放到消息队列中,由消费者来操作缓存。
- 消息队列保证可靠性:写到队列中的消息,成功消费之前不会丢失(重启项目也不担心)
- 消息队列保证消息成功投递:下游从队列拉取消息,成功消费后才会删除消息,否则还会继续投递消息给消费者(符合我们重试的场景)
至于写队列失败和消息队列的维护成本问题:
- 写队列失败:操作缓存和写消息队列,同时失败的概率其实是很小的
- 维护成本:我们项目中一般都会用到消息队列,维护成本并没有新增很多
另一种方式:订阅数据库变更日志,再操作缓存
拿 MySQL 举例,当一条数据发生修改时,MySQL 就会产生一条变更日志(Binlog),我们可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。
并发情况下的一致性问题
假设我们采用先更新数据库,再更新缓存的方案,并且两步都可以成功执行的前提下,如果存在并发,情况会是怎样的呢?
有线程 A 和线程 B 两个线程,需要更新「同一条」数据,会发生这样的场景:
- 线程 A 更新数据库(X = 1)
- 线程 B 更新数据库(X = 2)
- 线程 B 更新缓存(X = 2)
- 线程 A 更新缓存(X = 1)
最终 X 的值在缓存中是 1,在数据库中是 2,发生不一致。
也就是说,A 虽然先于 B 发生,但 B 操作数据库和缓存的时间,却要比 A 的时间短,执行时序发生错乱,最终这条数据结果是不符合预期的。同样先更新缓存、在更新数据库的方案也会出现类似问题。
每次数据发生变更,都更新缓存,但是缓存中的数据不一定会被马上读取,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。
所以此时我们需要考虑另外一种方案:删除缓存。
删除缓存可以保证一致性吗
删除缓存对应的方案也有 2 种:
- 先删除缓存,后更新数据库
- 先更新数据库,后删除缓存
但凡第二步操作失败,都会导致数据不一致。
所以我们这里来看在并发的情况下:
先删除缓存,后更新数据库
如果有 2 个线程要并发读写数据,可能会发生以下场景:
- 线程 A 要更新 X = 2(原值 X = 1)
- 线程 A 先删除缓存
- 线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1)
- 线程 A 将新值写入数据库(X = 2)
- 线程 B 将旧值写入缓存(X = 1)
最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),发生不一致。
可见,先删除缓存,后更新数据库,当发生读+写并发时,还是存在数据不一致的情况。
先更新数据库,后删除缓存
- 缓存中X不存在(数据库X=1)
- 线程A读取数据库,得到旧值(X=1)
- 线程B更新数据库(X=2)
- 线程B删除缓存
- 线程A将旧值写入缓存(X=1)
最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),也发生不一致。
此时满足数据不一致的三个条件
- 缓存刚好已失效
- 读请求 + 写请求并发
- 更新数据库+删除缓存的时间(步骤 3-4),要比读数据库 + 写缓存时间短(步骤 2 和 5)
其实此时发生的概率是非常低的,因为写数据库一般会先加锁,所以写数据库,通常是要比读数据库的时间更长的。
主从库延迟和延迟双删问题
**问题一:**上面两种情况都存在旧值重入
问题二:
在先更新数据库,再删除缓存方案下,读写分离 + 主从库延迟其实也会导致不一致:
- 线程 A 更新主库 X = 2(原值 X = 1)
- 线程 A 删除缓存
- 线程 B 查询缓存,没有命中,查询从库得到旧值(从库 X = 1)
- 从库同步完成(主从库 X = 2)
- 线程 B 将旧值写入缓存(X = 1)
最终 X 的值在缓存中是 1(旧值),在主从库中是 2(新值),也发生不一致。
解决第一个问题:在线程 A 删除缓存、更新完数据库之后,先休眠一会,再删除一次缓存。
解决第二个问题:线程 A 可以生成一条延时消息,写到消息队列中,消费者延时删除缓存。
这两个方案的目的,都是为了把缓存清掉,这样一来,下次就可以从数据库读取到最新值,写入缓存。****
边栏推荐
- PAT甲级 1044 Shopping in Mars
- spark-streaming状态流之mapWithState
- vlang捣鼓之路
- [physical simulation] ultra simple shape matching simulates rigid body motion
- [ten thousand words long text] Based on LSM tree thought Net 6.0 C # realize kV database (case version)
- What is the complexity often said during the interview?
- Final consistency distributed transaction TCC
- 初识OpenGL (4)链接着色器
- DTS is equipped with a new self-developed kernel, which breaks through the key technology of the three center architecture of the two places Tencent cloud database
- Nacos win10 安装配置教程
猜你喜欢
Final consistency distributed transaction TCC

PAT甲级 1047 Student List for Course

基于sisotool极点配置PI参数及基于Plecs的三相电压源逆变器仿真

DTS搭载全新自研内核,突破两地三中心架构的关键技术|腾讯云数据库

可信隐私计算框架“隐语”开源专家观点集锦

Tdengine landed in GCL energy technology, with tens of billions of data compressed to 600gb

Sql语句——单行注释与多行注释

结构体和类使用的区别

Mapwithstate of spark streaming state flow

2022 Niuke summer multi school training camp 1 (acdgij)
随机推荐
Advanced CAD exercises (I)
Re7: reading papers fla/mlac learning to predict charges for critical cases with legal basis
How to write unit tests
2022 Niuke summer multi school training camp 2 (bdghjkl)
I would like to ask you guys, how to specify the character set of MySQL CDC tables? I can't find the corresponding connector parameters on the official website. I read one
Differences between the use of structs and classes
srec_cat 常用参数的使用
综合设计一个OPPE主页--布局与初始化
Pat grade a 1045 favorite color stripe
movable-view 组件(可上下左右拖动 )
Happy 10th birthday, clojure
C#事件和委托的区别
vscode批量删除
Mapwithstate of spark streaming state flow
2022牛客暑期多校训练营1(ACDGIJ)
Re8:读论文 Hier-SPCNet: A Legal Statute Hierarchy-based Heterogeneous Network for Computing Legal Case
Internet Protocol
Comprehensively design an oppe homepage -- the design of the top and head
Vscode batch delete
NUC 11构建 ESXi 7.0.3f安装网卡驱动-V2(2022年7月升级版)