Redis如何保障缓存与数据库的数据一致性问题?

news2024/12/28 11:56:26

目录

一.最经典的数据库加缓存的双写双删模式

二. 高并发场景下的缓存+数据库双写不一致问题分析与解决方案设计

三、上面高并发的场景下,该解决方案要注意的问题


一.最经典的数据库加缓存的双写双删模式

1.1 Cache Aside Pattern概念以及读写逻辑

(1)读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应
(2)更新的时候,先删除缓存,然后再更新数据库

1.22、为什么是删除缓存,而不是更新缓存呢?

原因很简单,很多时候,复杂点的缓存的场景,因为缓存有的时候不简单是数据库中直接取出来的值,可能需要比较复杂的计算,甚至进行很多网络请求以及DB请求(比如我们有个缓存就是查微信的公共库以及我们自己的私有库联合组成一个缓存),这种更新缓存的代价很高的,但是呢我们更新完了缓存这个缓存,这个缓存也不一定立马就有人用,可能我更新了很多次数据库更新了很多次缓存都没人访问,这就导致了我服务器做了很多无用的计算

二. 高并发场景下的缓存+数据库双写不一致问题分析与解决方案设计

这里围绕和结合实时性较高的库存服务,把数据库与缓存双写不一致问题以及其解决方案,给大家讲解一下.

我们有两个操作顺序可以选择,其中都存在各种双鞋不一致情况,具体讨论讨论

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

2.1先删除缓存再更新数据库方式

2.1.1 上面说的最经典的方式有什么缓存不一致的问题?解决方案是什么?

问题:如果我们的方案是先修改数据库库存,再删除缓存,那么如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据出现不一致。

解决思路:
先删除缓存,再修改数据库,如果删除缓存成功了,如果修改数据库失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致,因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中

注意这里无并发读写没问题,但是并发情况下依然会有问题,我们继续往下看

2.2.2 上面第一个解决方案在并发下还是有问题

如果先删除缓存再删除数据库可能存在这种情况

  1. A服务删除缓存成功
  2. B请求来了读旧数据库存
  3. A更新新的库存成功

这样依然是数据库和缓存的库存不一致了

2.3 如何允许短暂的不一致,我们可以用什么思路来做?

2.3.1 基于MQ的分布式事务实现最终一致性

2.3.2 基于binlog监听实现

2.3.3 延迟双删 (比上面稍微优点的一点在于这里不需要印入MQ)

延时双删
延时双删的方案的思路是,为了避免更新数据库的时候,其他线程从缓存中读取不到数据,就在更新完数据库之后,再 Sleep 一段时间,然后再次删除缓存。
Sleep 的时间要对业务读写缓存的时间做出评估,Sleep 时间大于读写缓存的时间即可。
流程如下:
线程1删除缓存,然后去更新数据库。
线程2来读缓存,发现缓存已经被删除,所以直接从数据库中读取,这时候由于线程1还没有更新完成,所以读到的是旧值,然后把旧值写入缓存。
线程1,根据估算的时间,Sleep,由于 Sleep 的时间大于线程2读数据+写缓存的时间,所以缓存被再次删除。

如果还有其他线程来读取缓存的话,就会再次从数据库中读取到最新值。

高并发下又要求强一致性的解决思路:将统一商品的请求进行串行化

三、上面高并发的场景下,该解决方案要注意的问题

3.1读请求长时阻塞

由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。

该解决方案,最大的风险点在于说,可能数据更新很频繁,导致队列中积压了大量更新操作在里面然后读请求会发生大量的超时,最后导致大量的请求直接走数据库。

务必通过一些模拟真实的测试,看看更新数据的频繁是怎样的。

因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况
进行测试,可能需要部署多个服务,每个服务分摊一些数据的更新操作。

如果一个内存队列里居然会挤压100个商品的库存修改操作,每隔库存修改操作要耗费10ms
区完成,那么最后一个商品的读请求,可能等待10 * 100 = 1000ms = 1s后,才能得到
数据。

这个时候就导致读请求的长时阻塞。

一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看
最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的
读请求,会hang多少时间,如果读请求在200ms返回,如果你计算过后,哪怕是最繁忙的
时候,积压10个更新操作,最多等待200ms,那还可以的。

如果一个内存队列可能积压的更新操作特别多,那么你就要加机器,让每个机器上部署的
服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少。

其实根据之前的项目经验,一般来说数据的写频率是很低的,因此实际上正常来说,在队
列中积压的更新操作应该是很少的。

针对读高并发,读缓存架构的项目,一般写请求相对读来说,是非常非常少的,每秒的QPS
能到几百就不错了。

一秒,500的写操作,5份,每200ms,就100个写操作。

单机器,20个内存队列,每个内存队列,可能就积压5个写操作,每个写操作性能测试后,
一般在20ms左右就完成。

那么针对每个内存队列中的数据的读请求,也就最多hang一会儿,200ms以内肯定能返回了

写QPS扩大10倍,但是经过刚才的测算,就知道,单机支撑写QPS几百没问题,那么就扩容
机器,扩容10倍的机器,10台机器,每个机器20个队列,200个队列。

大部分的情况下,应该是这样的,大量的读请求过来,都是直接走缓存取到数据的

少量情况下,可能遇到读跟数据更新冲突的情况,如上所述,那么此时更新操作如果先入
队列,之后可能会瞬间来了对这个数据大量的读请求,但是因为做了去重的优化,所以也就
一个更新缓存的操作跟在它后面。

等数据更新完了,读请求触发的缓存更新操作也完成,然后临时等待的读请求全部可以读到
缓存中的数据。

3.2 读请求并发量过高

必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时hang在服务上,看服务能不能抗的住,需要多少机器才能抗住最大的极限情况的峰值。

但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也
就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。

按99:1的比例计算读和写的请求,每秒5万的读QPS,可能只有500次更新操作。

如果一秒有500的写QPS,那么要测算好,可能写操作影响的数据有500条,这500条数据在
缓存中失效后,可能导致多少读请求,发送读请求到库存服务来,要求更新缓存,这些读
请求每个会hang多长时间?

如果我们写读比例是1:20,每秒更新500条数据,这500秒数据对应的读请求,会有20 * 
500 = 1万,1万个读请求全部hang在库存服务上,就死定了。

3.3 多服务实例部署的请求路由一致性问题

可能这个服务部署了多个实例,那么必须保证,同一个商品id(我们路由到queue的规则),执行数据更新库存操作,以及执行缓存更新操作的请求,都通过nginx服务器路由到相同的服务实例上(这个要改nginx的hash路由规则)

如果一个商品的库存更新操作在A服务器的queue里,他的读路由到另一个服务器的队列里去了,就不需要串行化了。

3.4 热点商品的路由问题,导致请求的倾斜

万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能造成某台机器的压力过大。

其实只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些,需要注意。

3.5 .串行化缺点

一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案:读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。

但是呢:串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的请求。


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/441165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

redis非关系型数据库部署和使用(linux)

1.概念 NoSQL非关系型数据库是一种不使用关系模型来组织数据的数据库,通常用于存储非结构化或半结构化的数据,不支持或只部分支持SQL语言,满足最终一致性。非关系型数据库有多种类型,例如键值数据库、文档数据库、列式数据库、图形…

Shopee、Grab、Gojek 打造超级app已成为主流

超级App的概念在全球范围内逐渐被接受和采用。 超级App是指一种综合性的应用程序,允许用户在同一个平台上访问多个不同的服务,包括支付、社交媒体、出行、点餐等等。它的发源地是东南亚地区,如中国的微信、印度的Paytm和印尼的Gojek等应用&a…

Spring入门案例--bean的生命周期

bean的生命周期 关于bean的相关知识还有最后一个是bean的生命周期,对于生命周期,我们主要围绕着bean生命周期控 制 来讲解: 首先理解下什么是生命周期? 从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期。 bean生命周期是什么? bean对…

C++ | 说说类中的static成员

【概念】:声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化 文章目录 一、面试题引入二、static特…

5个实用的JavaScript原生API

本文带来5个难得一见的JavaScript原生API,为我们的前端开发带来意想不到的便利。 1. getBoundingClientRect() Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,该对象提供有关元素大小及其相对于视口的位置的信息。 domRect element.ge…

Java笔记_11(常用API)

Java笔记_11 一、常用的API1.1、MathMath练习 1.2、System1.3、Runtime1.4、Object1.5、浅克隆、深克隆1.6、对象工具类的Objects1.7、BigInteger(大整数)1.8、BigDecimal(大小数) 二、正则表达式2.1、正则表达式基础知识2.2、正则…

关于WordPress的20个有趣事实

时值 2022 年,互联网格局和 WordPress 的流行发生了重大变化。COVID-19 流行几乎影响到人类生存的方方面面,包括我们的互联网习惯,这也不例外。 到 2022 年,我们在家工作的人数显着增加,下岗或发现自己有更多空闲时间…

Python基础实战3-Pycharm安装简介

Pycharm下载、安装与使用 1.打开pycharm官网:下载 PyCharm: Python IDE for Professional Developers by JetBrains 2.选择自己对应的操作系统,点击Download,默认是最新版本,想安装其他版本可以选择Other versions下载…

【iOS】—— Masonry源码学习(浅看,未完)

Masonry 文章目录 MasonryNSLayoutConstraint用法Masonry源码 Masonry在我们之前的学习中是一个非常有用的第三方库。 Masonry是一种基于Objective-C语言的轻量级布局框架,它可以简化iOS应用程序中的自动布局任务。Masonry提供了一个方便的API,可以编写更…

Kubernetes Service、Ingress

Service(4层负载均衡器) 1、K8S 可以保证任意 Pod 挂掉时自动从任意节点启动一个新的Pod进行代替,以及某个Pod超负载时动态对Pod进行扩容。每当 Pod 发生变化时其 IP地址也会发生变化,且Pod只有在K8S集群内部才可以被访问&#xf…

Flink高手之路4-Flink流批一体

文章目录 Flink高手之路4-Flink流批一体API开发一、流批一体相关的概念1.数据的时效性2.流处理和批处理1)批处理2)流处理3)两者对比 3.流批一体API4.流批一体的编程模型 二、Data Source1.预定义的Source1)基于集合的Sources(1)API(2)演示 2)基于文件的Source(1)API(2)演示 3)基…

2023.4.19 + 4.20

文章目录 String类1:介绍:2:String类实现了很多的接口:3;String类常用构造器4:不同方式创建String类对象的区别(1)直接赋值的方式(2)常规new的方式&#xff0…

【筛质数】——朴素筛,埃式筛,欧拉筛

题目描述: 题目分析: 这道题可以用,朴素筛,埃氏筛,欧拉筛来写。 普通筛: 时间复杂度:O(n logn) 时间复杂度太高,会超时的!!(9/10) #…

Keil5----显示空白符和设置使用空白格表示Tab键

一、Keil5界面----显示空白符 首先打开Keil5-MDK界面,然后按照下面步骤操作。 步骤1:点击 Edit(编辑),然后点击 Configuration(配置) 步骤2:勾选 View White Spaces(查看空白) 步骤3:显示设置后的结果 具体显示结果分…

Git添加SSH密钥本地仓库上传远程GitHub库

1、前言 现在想要从本地设备将本地仓库上传到GitHub上需要用到SSH密钥,接下来讲解大致的步骤,本文默认读者已经掌握基本的Git知识 2、详细步骤 2.1 创建密钥 在本地项目仓库根目录下,输入下面的命令: ssh-keygen -t rsa命令输…

深度学习 Day 31——YOLOv5-Backbone模块实现

深度学习 Day 31——YOLOv5-Backbone模块实现 文章目录 深度学习 Day 31——YOLOv5-Backbone模块实现一、前言二、我的环境三、什么是YOLOv5-Backbone模块?四、搭建包含Backbone模块的模型1、模型整体代码2、模型每一部分详解3、模型详情 五、模型训练六、最终结果1…

计算机|网页设计 |七大罪动漫主题|作品分享

文章目录 一、主题介绍二、截图展示三、源代码获取 一、主题介绍 计算机|网页设计 |七大罪动漫主题|作品分享 一个关于七大罪动漫主题的网页设计。共4页 图片文字都可修改! 二、截图展示 三、源代码获取 本次的分享就到这里啦&…

双指针【算法推导、背模板】——最长连续不重复子序列

799. 最长连续不重复子序列 - AcWing题库 通常情况双指针就是需要将O(N^2^)&#xff0c;利用某些单调性质实现O(N) 通用代码模板 for(int i 0 , j 0; i < n ; i ){while(j < i && check(i , j ) ) j ;// 需要处理的逻辑 }check判断是否构成 算法推导 题目中…

LLM总结(持续更新中)

引言 当前LLM模型火出天际&#xff0c;但是做事还是需要脚踏实地。此文只是日常学习LLM&#xff0c;顺手整理所得。本篇博文更多侧重对话、问答类LLM上&#xff0c;其他方向&#xff08;代码生成&#xff09;这里暂不涉及&#xff0c;可以去看综述来了解。 之前LLM模型梳理 …

微服务---RabbitMQ与SpringAMQP基本使用

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…