ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

浅谈缓存数据库双写一致性

2021-10-11 15:02:55  阅读:138  来源: 互联网

标签:缓存 浅谈 删除 数据库 更新 thread 线程 双写


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 删除缓存还是更新缓存?

  1. 线程A先发起一个写操作,第一步先更新数据库
  2. 线程B再发起一个写操作,第二步更新了数据库
  3. 由于网络等原因,线程B先更新了缓存
  4. 线程A更新缓存。

这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了,脏数据出现啦。如果是删除缓存取代更新缓存则不会出现这个脏数据问题。

更新缓存相对于删除缓存,还有两点劣势:

  • 如果你写入的缓存值,是经过复杂计算才得到的话。更新缓存频率高的话,就浪费性能啦。
  • 在写数据库场景多,读数据场景少的情况下,数据很多时候还没被读取到,又被更新了,这也浪费了性能呢(实际上,写多的场景,用缓存也不是很划算了)

2.2 先删除缓存还是先更新数据库?

我们在操作缓存的时候,到底应该先删除缓存还是先更新数据库呢?我们先来看个例子:

  1. 缓存已经失效
  2. 线程A查询数据库,得一个旧值
  3. 线程B将新值写入数据库
  4. 然后线程B删除缓存
  5. 线程A将查到的旧值写入缓存

一般来说读比写快很多,第二步和第五步之间插入写动作概率不大。

2.3 先删除缓存,再更新数据库

  1. 线程A先发起一个写操作,先删除了缓存
  2. 线程B再发起一个读操作,发现缓存不存在
  3. 线程B查询旧值后放入缓存
  4. 线程A更新了数据库

缓存和数据库的数据不一致了。缓存保存的是老数据,数据库保存的是新数据。因此,Cache-Aside缓存模式,选择了先操作数据库而不是先操作缓存。

解决方法:

  • 先删除缓存,再更新数据库,休眠一会再删缓存(双删,第二次删可异步)

这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒。 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。

2.3.1 删除缓存重试机制

不管是延时双删还是Cache-Aside的先操作数据库再删除缓存,如果第二步的删除缓存失败呢,删除失败会导致脏数据哦~

删除失败就多删除几次呀,保证删除缓存成功呀~ 所以可以引入删除缓存重试机制

image.png

  1. 写请求更新数据库
  2. 缓存因为某些原因,删除失败
  3. 把删除失败的key放到消息队列
  4. 消费消息队列的消息,获取要删除的key
  5. 重试删除缓存操作

2.4 17boot-cache解决了什么

对于2.3的情况,第一步A的事务没有结束,所以@CacheMeta并没有删除缓存,所以第二步B去读还是能读到缓存,避免了将缓存更新成旧缓存的问题。

但是还是无法避免2.2的情况,其实先操作db还是cache,都会有各自的问题,根本原因是cache和db的更新不是一个原子操作,因此总会有不一致的问题。想要彻底解决这种问题必须将cache和db的更新操作归在一个事务之下(例如使用一些分布式事务,或者强一致性的分布式协议),可能反而得不偿失。

在RedisCacheManager中设置transactionAware也能实现

在这里插入图片描述

加入template.setEnableTransactionSupport(true);即可打开redis事务

标签:缓存,浅谈,删除,数据库,更新,thread,线程,双写
来源: https://blog.csdn.net/sinat_29774479/article/details/120703317

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有