浅谈缓存数据库双写一致性
2021/10/11 19:14:40
本文主要是介绍浅谈缓存数据库双写一致性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1. 事务完善双写一致性
17boot-cache引入了@CacheMeta来标注缓存,但是spring的已经有现成的缓存,为什么要新写一个注解去做这个事呢,在cf中提出了一个场景,@CacheMeta就是为了解决这个问题:
1 thread-1 根据id删除表中数据 2 thread-1 根据id清除缓存 3 thread-2 根据id从缓存中查找数据 4 thread-2 缓存中找不到,查询数据库,拿到的是老数据,并加载到缓存 5 thread-1 事务提交
在这个情况下,thread-1拿到就是thread-2放入的旧数据
那如何解决这个问题,他们给出了以下方法
1 thread-1 根据id删除表中数据 2 thread-1 标记该id需要删除 // 只是标记,还未真正发起对缓存的操作 3 thread-2 从缓存中查找数据,找到缓存,返回 4 thread-1 事务提交 5 thread-1 调用spring事务成功回调方法,正式清除该id对应的缓存
CacheMeta的解决方法是事务未结束时对缓存数据进行标记删除,只有当事务返回成功之后才会正式清除对应的缓存。其中标记删除可以进行批量操作,减少交互。
其实这个问题是经典的并发下的问题:如何保障数据库缓存双写一致性
2.数据库缓存双写一致性解决方案
2.1 删除缓存还是更新缓存?
- 线程A先发起一个写操作,第一步先更新数据库
- 线程B再发起一个写操作,第二步更新了数据库
- 由于网络等原因,线程B先更新了缓存
- 线程A更新缓存。
这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了,脏数据出现啦。如果是删除缓存取代更新缓存则不会出现这个脏数据问题。
更新缓存相对于删除缓存,还有两点劣势:
- 如果你写入的缓存值,是经过复杂计算才得到的话。更新缓存频率高的话,就浪费性能啦。
- 在写数据库场景多,读数据场景少的情况下,数据很多时候还没被读取到,又被更新了,这也浪费了性能呢(实际上,写多的场景,用缓存也不是很划算了)
2.2 先删除缓存还是先更新数据库?
我们在操作缓存的时候,到底应该先删除缓存还是先更新数据库呢?我们先来看个例子:
- 缓存已经失效
- 线程A查询数据库,得一个旧值
- 线程B将新值写入数据库
- 然后线程B删除缓存
- 线程A将查到的旧值写入缓存
一般来说读比写快很多,第二步和第五步之间插入写动作概率不大。
2.3 先删除缓存,再更新数据库
- 线程A先发起一个写操作,先删除了缓存
- 线程B再发起一个读操作,发现缓存不存在
- 线程B查询旧值后放入缓存
- 线程A更新了数据库
缓存和数据库的数据不一致了。缓存保存的是老数据,数据库保存的是新数据。因此,Cache-Aside
缓存模式,选择了先操作数据库而不是先操作缓存。
解决方法:
- 先删除缓存,再更新数据库,休眠一会再删缓存(双删,第二次删可异步)
这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒。 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。
2.3.1 删除缓存重试机制
不管是延时双删还是Cache-Aside的先操作数据库再删除缓存,如果第二步的删除缓存失败呢,删除失败会导致脏数据哦~
删除失败就多删除几次呀,保证删除缓存成功呀~ 所以可以引入删除缓存重试机制
- 写请求更新数据库
- 缓存因为某些原因,删除失败
- 把删除失败的key放到消息队列
- 消费消息队列的消息,获取要删除的key
- 重试删除缓存操作
2.4 17boot-cache解决了什么
对于2.3的情况,第一步A的事务没有结束,所以@CacheMeta并没有删除缓存,所以第二步B去读还是能读到缓存,避免了将缓存更新成旧缓存的问题。
但是还是无法避免2.2的情况,其实先操作db还是cache,都会有各自的问题,根本原因是cache和db的更新不是一个原子操作,因此总会有不一致的问题。想要彻底解决这种问题必须将cache和db的更新操作归在一个事务之下(例如使用一些分布式事务,或者强一致性的分布式协议),可能反而得不偿失。
在RedisCacheManager中设置transactionAware也能实现
加入template.setEnableTransactionSupport(true);即可打开redis事务
这篇关于浅谈缓存数据库双写一致性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-07-04TiDB 资源管控的对撞测试以及最佳实践架构
- 2024-07-03万字长文聊聊Web3的组成架构
- 2024-07-02springboot项目无法注册到nacos-icode9专业技术文章分享
- 2024-06-26结对编程到底难不难?答案在这里
- 2024-06-19《2023版Java工程师》课程升级公告
- 2024-06-15matplotlib作图不显示3D图,怎么办?
- 2024-06-1503-Loki 日志监控
- 2024-06-1504-让LLM理解知识 -Prompt
- 2024-06-05做软件测试需要懂代码吗?
- 2024-06-0514-ShardingSphere的分布式主键实现