如何保证Redis缓存与数据库的一致性?
目录
- 1、四种同步策略:
- 2、更新缓存还是删除缓存
- 2.1 更新缓存
- 2.2 删除缓存
- 3、先操作数据库还是缓存
- 3.1 先删除缓存再更新数据库
- 3.2 先更新数据库再删除缓存
- 4、延时双删
- 4.1 采用读写分离的架构怎么办?
- 5、利用消息队列进行删除的补偿
1、四种同步策略:
想要保证缓存与数据库的双写一致,一共有4种方式,即4种同步策略:
- 先更新缓存,再更新数据库;
- 先更新数据库,再更新缓存;
- 先删除缓存,再更新数据库;
- 先更新数据库,再删除缓存。
从这4种同步策略中,我们需要作出比较的是:
更新缓存与删除缓存哪种方式更合适?应该先操作数据库还是先操作缓存?
2、更新缓存还是删除缓存
下面,我们来分析一下,应该采用更新缓存还是删除缓存的方式。
2.1 更新缓存
优点:每次数据变化都及时更新缓存,所以查询时不容易出现未命中的情况。
缺点:更新缓存的消耗比较大。如果数据需要经过复杂的计算再写入缓存,那么频繁的更新缓存,就会影响服务器的性能。如果是写入数据频繁的业务场景,那么可能频繁的更新缓存时,却没有业务读取该数据。
2.2 删除缓存
优点:操作简单,无论更新操作是否复杂,都是将缓存中的数据直接删除。
缺点:删除缓存后,下一次查询缓存会出现未命中,这时需要重新读取一次数据库。从上面的比较来看,一般情况下,删除缓存是更优的方案。
3、先操作数据库还是缓存
下面,我们再来分析一下,应该先操作数据库还是先操作缓存。
首先,我们将先删除缓存与先更新数据库,在出现失败
时进行一个对比:
3.1 先删除缓存再更新数据库
如上图,是先删除缓存再更新数据库,在出现失败时可能出现的问题:
- 线程A删除缓存成功,线程A更新数据库失败;
- 线程B从缓存中读取数据;由于缓存被删,进程B无法从缓存中得到数据,进而从数据库读取数据;此时数据库中的数据更新失败,线程B从数据库成功获取旧的数据,然后将数据更新到了缓存。
- 最终,缓存和数据库的数据是一致的,但仍然是旧的数据
3.2 先更新数据库再删除缓存
如上图,是先更新数据库再删除缓存,在出现失败
时可能出现的问题:
- 线程A更新数据库成功,线程A删除缓存失败;
- 线程B读取缓存成功,由于缓存删除失败,所以线程B读取到的是缓存中旧的数据。
- 最后线程A删除缓存成功,有别的线程访问缓存同样的数据,与数据库中的数据是一样。
- 最终,缓存和数据库的数据是一致的,但是会有一些线程读到旧的数据。
经过上面的比较,我们发现在出现失败
的时候,是无法明确分辨出先删缓存和先更新数据库哪个方式更好,以为它们都存在问题。后面我们会进一步对这两种方式进行比较,但是在这里我们先探讨一下,上述场景出现的问题,应该如何解决呢?
实际上,无论上面我们采用哪种方式去同步缓存与数据库,在第二步出现失败的时候,都建议采用重试机制解决,上面两幅图中已经画了。
下面我们再将先删缓存与先更新数据库,在没有出现失败时
进行对比:
如上图,是先删除缓存再更新数据库,在没有出现失败时
可能出现的问题:
- 线程A删除缓存成功;
- 线程B读取缓存失败;
- 线程B读取数据库成功,得到旧的数据;
- 线程B将旧的数据成功地更新到了缓存;
- 线程A将新的数据成功地更新到数据库。
可见,进程A的两步操作均成功,但由于存在并发,在这两步之间,进程B访问了缓存。最终结果是,缓存中存储了旧的数据,而数据库中存储了新的数据,二者数据不一致。
如上图,是先更新数据库再删除缓存,在没有出现失败时
可能出现的问题:
- 线程A更新数据库成功;
- 线程B读取缓存成功;
- 线程A删除缓存成功。
可见,最终缓存与数据库的数据是一致的,并且都是最新的数据。但线程B在这个过程里读到了旧的数据,可能还有其他线程也像线程B一样,在这两步之间读到了缓存中旧的数据,但因为这两步的执行速度会比较快,所以影响不大。对于这两步之后,其他进程再读取缓存数据的时候,就不会出现类似于进程B的问题了。
最终结论:
经过对比你会发现,先更新数据库、再删除缓存是影响更小的方案。如果第二步出现失败的情况,则可以采用重试机制解决问题。
4、延时双删
上面我们提到,如果是先删缓存、再更新数据库,在没有出现失败时可能会导致数据的不一致。如果在实际的应用中,出于某些考虑我们需要选择这种方式,那有办法解决这个问题吗?答案是有的,那就是采用延时双删的策略,延时双删的基本思路如下:
- 删除缓存;
- 更新数据库;
- sleep N毫秒;
- 再次删除缓存。
public void write(String key, Object data) {Redis.delKey(key);db.updateData(data);Thread.sleep(1000);Redis.delKey(key);}
阻塞一段时间之后,再次删除缓存,就可以把这个过程中缓存中不一致的数据删除掉。而具体的时间,要评估你这项业务的大致时间,按照这个时间来设定即可。
4.1 采用读写分离的架构怎么办?
如果数据库采用的是读写分离的架构,那么又会出现新的问题,如下图:
此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)
- 请求 A 更新操作,删除了 Redis;
- 请求主库进⾏更新操作,主库与从库进行同步数据的操作;
- 请 B 查询操作,发现 Redis 中没有数据;
- 去从库中拿去数据;
- 此时同步数据还未完成,拿到的数据是旧数据;
此时的解决办法就是如果是对 Redis 进行填充数据的查询数据库操作,那么就强制将其指向主库进⾏查询。
删除失败了怎么办?
如果删除依然失败,则可以增加重试的次数,但是这个次数要有限制,当超出一定的次数时,要采取报错、记日志、发邮件提醒等措施。
5、利用消息队列进行删除的补偿
先更新数据库,后删除缓存这⼀种情况也会出现问题,比如更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功,那么此时再读取缓存的时候每次都是错误的数据了。
此时解决方案就是利用消息队列进行删除的补偿。具体的业务逻辑⽤语⾔描述如下:
- 请求 线程A 先对数据库进行更新操作;
- 在对 Redis 进行删除操作的时候发现报错,删除失败;
- 此时将Redis 的 key 作为消息体发送到消息队列中;
- 系统接收到消息队列发送的消息后再次对 Redis 进行删除操作;
但是这个方案会有⼀个缺点就是会对业务代码造成大量的侵入,深深的耦合在⼀起,所以这时会有⼀个优化的方法,我们知道对 Mysql 数据库更新操作后再 binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。
如何保证Redis缓存与数据库的一致性?相关推荐
- 如何保证缓存和数据库的一致性?
文章目录 1. 问题分析 2. Cache-Aside 2.1 读缓存 2.2 写缓存 2.3 延迟双删 2.4 如何确保原子性 3. Read-Through/Write-Through 3.1 R ...
- 缓存与数据库的一致性:先操作缓存还是先操作数据库?
数据缓存 在我们实际的业务场景中,一定有很多需要做数据缓存的场景,比如售卖商品的页面,包括了许多并发访问量很大的数据,它们可以称作是是"热点"数据,这些数据有一个特点,就是更新频率 ...
- 如何保障缓存和数据库的一致性(超详细案例)【转载自 程序员囧辉】
如何保障缓存和数据库的一致性(超详细案例)[转载自 程序员囧辉] 一.前言 二.正文 方案1:同步删除 1.1 核心流程 1.2 存在的问题 方案2:延迟双删 2.1 核心流程 2.2 存在的问题 方 ...
- 预约活动(秒杀)项目中如何高效的保证下单交易成功?保证redis,mysql的最终一致性?
预约活动(秒杀)项目中如何高效的保证下单交易成功?保证redis,mysql的最终一致性? 前言 `在秒杀项目中,秒杀下单的过程中都需要经历四步,分别为: 1.校验下单状态,商品是否存在,用户是否合法 ...
- 链接mysql_使用python链接mysql及redis(缓存型数据库)
python链接数据库mysql操作,首先我们需要借助第三方库pymysql. cmd终端下载:pip install pymysql import pymysql#链接数据库db = pymysql ...
- Redis缓存与数据库双写一致性
前言: 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作. 但是在更新缓存方面,对于更新完数据库,是更新缓存 ...
- 缓存与数据库的一致性
本文来说下缓存与数据库的双写一致性.分布式缓存是现在很多分布式应用中必不可少的组件,但是用到了分布式缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决 ...
- redis缓存跟数据库数据不一致问题解决
如何保证缓存与数据库的双写一致性? 最经典的缓存+数据库读写的模式. 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应. 更新的时候,先删除缓存,再更新数据库. 为什 ...
- 更新数据时redis缓存与数据库数据不一致的问题
最初级的缓存不一致问题及解决方案 问题:先修改数据库,再删除缓存.如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致. 解决思路:先删除缓存,再修改数据库.如果数据库修 ...
最新文章
- 4.API的调用过程(系统服务表)
- ap统计学需要什么计算机,准备AP统计学考试不需要死背公式
- java卡片布局源码_Java编程使用卡片布局管理器示例【基于swing组件】
- .NET 5 的 Target Framework 详解[上篇]
- AFNetworking 3.1.0 使用中某些知识点讲解
- 红魔游戏手机6 Pro氘锋透明版明日开启预售:售价5599元
- mysql 性能剖析_MySQL服务器性能剖析(一)
- apollo 部署 使用
- 深度学习:循环神经网络RNN
- day22 属性 类方法 静态方法 反射 https://www.cnblogs.com/jin-xin/articles/9214247.html
- [工业互联-6]:PLC工业控制系统快速概览
- 必读论文 | 机器交互必读论文8篇
- (c++ 遗传算法解决TSP问题)不是吧,这就是遗传算法吗?爱了爱了
- html判断是否在微信里打开,JavaScript判断浏览器内核,微信打开自动提示在浏览器打开...
- 【修真院“善良”系列之十七】请拿好这支时间之箭
- Python中Numpy中省略号的作用
- 第17课 项目成本管理
- 无法安装 cloudera-manager-agent
- 电商名词sku和spu的区别
- IDEA javadoc快捷键