十六、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

相关文章

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

Redis中Stream详解及应用小结

《Redis中Stream详解及应用小结》RedisStreams是Redis5.0引入的新功能,提供了一种类似于传统消息队列的机制,但具有更高的灵活性和可扩展性,本文给大家介绍Redis中Strea... 目录1. Redis Stream 概述2. Redis Stream 的基本操作2.1. XADD

SpringSecurity整合redission序列化问题小结(最新整理)

《SpringSecurity整合redission序列化问题小结(最新整理)》文章详解SpringSecurity整合Redisson时的序列化问题,指出需排除官方Jackson依赖,通过自定义反序... 目录1. 前言2. Redission配置2.1 RedissonProperties2.2 Red

nginx 负载均衡配置及如何解决重复登录问题

《nginx负载均衡配置及如何解决重复登录问题》文章详解Nginx源码安装与Docker部署,介绍四层/七层代理区别及负载均衡策略,通过ip_hash解决重复登录问题,对nginx负载均衡配置及如何... 目录一:源码安装:1.配置编译参数2.编译3.编译安装 二,四层代理和七层代理区别1.二者混合使用举例

SQL server数据库如何下载和安装

《SQLserver数据库如何下载和安装》本文指导如何下载安装SQLServer2022评估版及SSMS工具,涵盖安装配置、连接字符串设置、C#连接数据库方法和安全注意事项,如混合验证、参数化查... 目录第一步:打开官网下载对应文件第二步:程序安装配置第三部:安装工具SQL Server Manageme

C#连接SQL server数据库命令的基本步骤

《C#连接SQLserver数据库命令的基本步骤》文章讲解了连接SQLServer数据库的步骤,包括引入命名空间、构建连接字符串、使用SqlConnection和SqlCommand执行SQL操作,... 目录建议配合使用:如何下载和安装SQL server数据库-CSDN博客1. 引入必要的命名空间2.

Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式

《Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式》本文详细介绍如何使用Java通过JDBC连接MySQL数据库,包括下载驱动、配置Eclipse环境、检测数据库连接等关键步骤,... 目录一、下载驱动包二、放jar包三、检测数据库连接JavaJava 如何使用 JDBC 连接 mys

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操