十六、Redis和数据库双写一致性问题

2024-05-13 14:12

本文主要是介绍十六、Redis和数据库双写一致性问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

众所周知,Redis一般被用来做为数据的缓存中间件,提升系统读数据的能力。但是被缓存的数据并不是一成不变的。如果是永远不会变的,那不会存在双写一致性问题(只需构建一次缓存即可)。但是大部分情况下,或多或少都会涉及到缓存数据的变更的问题。这时就需要思考一个问题,到底是先操作数据库,还是先操作缓存。

image.png

1、先更新数据库,在更新缓存

这套方案是最被大家反对的方案。有以下几点原因:
原因一、同时有请求A和请求B进行更新操作,那么会出现

  1. 线程A更新了数据库
  2. 线程B更新了数据库
  3. 线程B更新了缓存
  4. 线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早,但是因为某种原因,B的更新却比A的更新早。这就造成了缓存中的脏数据出现。

2、先删缓存,在更新数据库

改方案导致不一致的原因是:同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么就会出现以下情形:

  1. 请求A删除缓存
  2. 请求B查询,发现缓存中不存在
  3. 请求B去查询数据库得到旧值
  4. 请求B将旧值写入缓存
  5. 请求A将新值写入到数据库

上面就会出现数据不一致问题,即使请求A先删掉缓存,这是请求B在A更新数据库前请求,就会出现又将旧值写到缓存的问题。就会导致数据库和缓存不一致的问题。
对于上面的问题,可以采用延时双删的策略。

redis.deleteKey(key)
db.update
Thread.sleep(500)
redis.deleteKey(key)
其实就是:
  1. 先删除缓存
  2. 更新数据库
  3. 休眠一段时间,在删除缓存。(允许在睡眠这段时间内的脏数据)

针对上面的情景,睡眠的时间应该根据自己的业务来进行判断,写数据的休眠时间应该在读数据的耗时基础上加上个几百毫秒即可。这样做的目的可以确保读请求结束后,写请求可以删除读请求造成的缓存脏数据。

但是如果是使用的mysql的读写分离架构怎么办?
那么在这种情况下,造成数据不一致的原因如下:

  1. 请求A写操作,删除缓存
  2. 请求A将数据写入主数据库
  3. 请求B查询缓存,发现不存在
  4. 请求B去从库查询得到旧值
  5. 请求B将旧值写到缓存
  6. 数据库完成主从同步,从库变为新值

上面这种情形,就是造成数据不一致的原因。请求B在主从同步之前,来请求这是还会将旧值写到缓存中,即使A请求删除掉了缓存。
还是使用延时双删策略。只是睡眠时间修改为在主从同步的延时时间上增加几百毫秒。

但是采用延时双删策略,会降低系统的吞吐量。
对于这个问题,可以将第二次删除采用异步的方式。这样写请求就不用睡眠一段时间在返回。

但是如果,第二次删除失败了会怎么样?
当请求A去删除请求B读取的旧值写到的缓存时失败,这是又会出现,缓存和数据库不一致的情况。这种情况就看下第三种更新策略。

3、先更新数据库,在删缓存

其实这种情况也存在并发问题。
假设有两个请求,一个请求A做查询请求,另一个请求B做更新请求,那么会有如下情况产生:

  1. 缓存刚好失效
  2. 请求A查询数据库,得到一个旧值
  3. 请求B将新值写入数据库
  4. 请求B删除缓存
  5. 请求A将查询的旧值更新到缓存

这种情形也会发生脏数据。
但是这种情形发生的概率是极低的。

发生上述情况的前提条件是步骤3的写数据操作比步骤2的查询操作耗时更短(只有这样才能在请求B在请求A之后写库,但是比A先执行完)。
但是一般来说,写的请求耗时是比读的请求更多的。总的来说这种情形很少出现。

但是假设如果一定会出现这种情形呢!如何处理呢?
首先,给缓存设置一个过期时间是一个有效的解决方案。另外也可以采用策略2中提到的延时双删策略。但是这个方法也会出现第二次删除失败的问题。回导致数据的不一致。

对于解决上述问题的方法,可以采用提供一个保障的重试机制方法。

方案一:采用失败发送MQ消息队列的方式,重试机制。
image.png
方案二:采用订阅数据库的binlog日志的方式
image.png

这篇关于十六、Redis和数据库双写一致性问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/985932

相关文章

mysql数据库重置表主键id的实现

《mysql数据库重置表主键id的实现》在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,本文主要介绍了mysql数据库重置表主键id的实现,具有一定的参考价值,感兴趣的可以了... 目录关键语法演示案例在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,当我们

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作

Redis 中的热点键和数据倾斜示例详解

《Redis中的热点键和数据倾斜示例详解》热点键是指在Redis中被频繁访问的特定键,这些键由于其高访问频率,可能导致Redis服务器的性能问题,尤其是在高并发场景下,本文给大家介绍Redis中的热... 目录Redis 中的热点键和数据倾斜热点键(Hot Key)定义特点应对策略示例数据倾斜(Data S

Spring Boot 整合 MyBatis 连接数据库及常见问题

《SpringBoot整合MyBatis连接数据库及常见问题》MyBatis是一个优秀的持久层框架,支持定制化SQL、存储过程以及高级映射,下面详细介绍如何在SpringBoot项目中整合My... 目录一、基本配置1. 添加依赖2. 配置数据库连接二、项目结构三、核心组件实现(示例)1. 实体类2. Ma

如何解决mysql出现Incorrect string value for column ‘表项‘ at row 1错误问题

《如何解决mysql出现Incorrectstringvalueforcolumn‘表项‘atrow1错误问题》:本文主要介绍如何解决mysql出现Incorrectstringv... 目录mysql出现Incorrect string value for column ‘表项‘ at row 1错误报错

如何解决Spring MVC中响应乱码问题

《如何解决SpringMVC中响应乱码问题》:本文主要介绍如何解决SpringMVC中响应乱码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC最新响应中乱码解决方式以前的解决办法这是比较通用的一种方法总结Spring MVC最新响应中乱码解

pip无法安装osgeo失败的问题解决

《pip无法安装osgeo失败的问题解决》本文主要介绍了pip无法安装osgeo失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 进入官方提供的扩展包下载网站寻找版本适配的whl文件注意:要选择cp(python版本)和你py

redis+lua实现分布式限流的示例

《redis+lua实现分布式限流的示例》本文主要介绍了redis+lua实现分布式限流的示例,可以实现复杂的限流逻辑,如滑动窗口限流,并且避免了多步操作导致的并发问题,具有一定的参考价值,感兴趣的可... 目录为什么使用Redis+Lua实现分布式限流使用ZSET也可以实现限流,为什么选择lua的方式实现

Redis中管道操作pipeline的实现

《Redis中管道操作pipeline的实现》RedisPipeline是一种优化客户端与服务器通信的技术,通过批量发送和接收命令减少网络往返次数,提高命令执行效率,本文就来介绍一下Redis中管道操... 目录什么是pipeline场景一:我要向Redis新增大批量的数据分批处理事务( MULTI/EXE