缓存和数据库一致性解决方案

引入缓存提高性能

如果你的业务处于起步阶段,流量非常小,那无论是读请求还是写请求,直接操作数据库即可,这时你的架构模型是这样的:

但随着业务量的增长,你的项目请求量越来越大,这时如果每次都从数据库中读数据,那肯定会有性能问题。

这个阶段通常的做法是,引入「缓存」来提高读性能,架构模型就变成了这样:

如何提高缓存利用率?

想要缓存利用率「最大化」,我们很容易想到的方案是,缓存中只保留最近访问的「热数据」。

  • 写请求依旧只写数据库
  • 读请求先读缓存,如果缓存不存在,则从数据库读取,并重建缓存
  • 同时,写入缓存中的数据,都设置失效时间

缓存中不经常访问的数据,随着时间的推移,都会逐渐「过期」淘汰掉,最终缓存中保留的,都是经常被访问的「热数据」,缓存利用率得以最大化。

如何保证缓存和数据库数据的一致性?

更新缓存和数据库有四种方案:

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

更新缓存

优点:
如果每次数据变化都能被及时更新,那么查询数据时不容易出现不命中的情况,
缺点:
1、如果数据的计算复杂,频繁的更新会造成服务器性能的消耗比较大
2、如果数据并不是被频繁使用,那么频繁更新也只是浪费服务器性能,对业务没有多大的帮助

删除缓存

优点:不需要顾忌数据的复杂性,直接删除即可
缺点:查询数据时,增大未命中的几率,从而增大数据库的访问压力
适用于数据使用频率不高的场景

先更新缓存,再更新 DB

这个方案一般不考虑。原因是更新缓存成功,更新数据库出现异常了, 导致缓存数据与数据库数据完全不一致,而且很难察觉,因为缓存中的数据一直都存在。

先更新 DB,再更新缓存

如果数据库更新成功了,但缓存更新失败,那么此时数据库中是最新值,缓存中是「旧值」。之后的读请求读到的都是旧数据,只有当缓存「失效」后,才能从数据库中得到正确的值。这种方案一般不考虑。

并发引发的一致性问题

同时有请求 A 和请求 B 进行更新操作,那么会出现

(1)线程 A 更新了数据库

(2)线程 B 更新了数据库

(3)线程 B 更新了缓存

(4)线程 A 更新了缓存

线程1 虽然先于 线程2 发生,但 线程2 操作数据库和缓存的时间,却要比 线程1 的时间短,执行时序发生「错乱」,最终这条数据结果是不符合预期的。

如果是写多读少的场景,采用 这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。

先删除缓存,后更新 DB

如果出现这样情况:
1、线程A更新数据库中的数据
2、线程A删除缓存中的数据,删除失败
3、线程B查询缓存中的数据,查询到旧数据
4、线程A异步重试删除缓存
这里,删除缓存中数据失败后就会造成线程B获取到缓存中的旧数据,从而导致数据不一致的情况。

还有并发情况:

来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

请求 A 会先删除 Redis 中的数据,然后去数据库进行更新操作;

此时请求 B 看到 Redis 中的数据时空的,会去数据库中查询该值,更新到Redis 中;

但是此时请求 A 并没有更新成功,或者事务还未提交,请求 B 去数据库查询得到旧值;

可见,先删除缓存,后更新数据库,当发生「读+写」并发时,还是存在数据不一致的情况。

如何解决呢?其 实最简单的解决办法就是延时双删的策略。

(1)先淘汰缓存

(2)再写数据库

(3)休眠 1 秒,再次淘汰缓存 这么做,可以将 1 秒内所造成的缓存脏数据,再次删除。

那么,这个 1 秒怎么确定的,具体该休眠多久呢? 针对上面的情形,自行评估自己的项目的读数据业务逻辑的耗时。然后写数 据的休眠时间则在读数据业务逻辑的耗时基础上,加几百 ms 即可。这么做的目 的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

如果Mysql 的读写分离的架构的话,那么其实主从同步之间也会有时间差。

请求 A(更新操作) 和请求 B(查询操作)

  1. 线程 A 更新主库 X = 2(原值 X = 1)
  2. 线程 A 删除缓存
  3. 线程 B 查询缓存,没有命中,查询「从库」得到旧值(从库 X = 1)
  4. 从库「同步」完成(主从库 X = 2)
  5. 线程 B 将「旧值」写入缓存(X = 1)

此时的解决办法有两个:

1、还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百 ms。

2、就是如果是对 Redis 进行填充数据的查询数据库操作,那么就强制将其指向主库进行查询。

这个「延迟删除」缓存,延迟时间到底设置要多久呢?

  • 问题1:延迟时间要大于「主从复制」的延迟时间
  • 问题2:延迟时间要大于线程 B 读取数据库 + 写入缓存的时间

但是,这个时间在分布式和高并发场景下,其实是很难评估的

很多时候,我们都是凭借经验大致估算这个延迟时间,例如延迟 1-5s,只能尽可能地降低不一致的概率。

所以你看,采用这种方案,也只是尽可能保证一致性而已,极端情况下,还是有可能发生不一致。

所以实际使用中,建议采用「先更新数据库,再删除缓存」的方案,同时,要尽可能地保证「主从复制」不要有太大延迟,降低出问题的概率。

先更新数据库,后删除缓存

先分析有失败情况:
1、线程A更新数据库中的数据
2、线程A删除缓存中的数据,删除失败
3、线程B查询缓存中的数据,查询到旧数据
4、线程A异步重试删除缓存
这里,删除缓存中数据失败后就会造成线程B获取到缓存中的旧数据,从而导致数据不一致的情况

假设这会有两个请求,一个请求 A 做查询操作,一个请求 B 做 更新操作,那么会有如下情形产生

(1)缓存刚好失效

(2)请求 A 查询数据库,得一个旧值

(3)请求 B 将新值写入数据库

(4)请求 B 删除缓存

(5)请求 A 将查到的旧值写入缓存

其实概率「很低」,这是因为它必须满足 3 个条件:

  1. 缓存刚好已失效
  2. 读请求 + 写请求并发
  3. 更新数据库 + 删除缓存的时间(步骤 3-4),要比读数据库 + 写缓存(步骤 2 和 5)时间短

仔细想一下,条件 3 发生的概率其实是非常低的。

因为写数据库一般会先「加锁」,所以写数据库,通常是要比读数据库的时间更长的。这么来看,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。

**更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功怎么办?**这个问题,在删除缓存类的方案都是存在的,那么此时再读取缓存的时候每次都是错误的数据了。

此时解决方案有两个,一是就是利用消息队列进行删除的补偿:

1、请求 A 先对数据库进行更新操作

2、在对 Redis 进行删除操作的时候发现报错,删除失败

3、此时将 Redis 的 key 作为消息体发送到消息队列中

4、系统接收到消息队列发送的消息后

5、再次对 Redis 进行删除操作

第二种方案,订阅数据库变更日志,再操作缓存

具体来讲就是,我们的业务应用在修改数据时,「只需」修改数据库,无需操作缓存。

那什么时候操作缓存呢?这就和数据库的「变更日志」有关了。

拿 MySQL 举例,当一条数据发生修改时,MySQL 就会产生一条变更日志(Binlog),我们可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。

想要保证数据库和缓存一致性,推荐采用「先更新数据库,再删除缓存」方案,并配合「消息队列」或「订阅变更日志」的方式来做

总结

引入缓存后,需要考虑缓存和数据库一致性问题,可选的方案有:「更新数据库 + 更新缓存」、「更新数据库 + 删除缓存」

更新数据库 + 更新缓存方案,在「并发」场景下无法保证缓存和数据一致性,且存在「缓存资源浪费」和「机器性能浪费」的情况发生

在更新数据库 + 删除缓存的方案中,「先删除缓存,再更新数据库」在「并发」场景下依旧有数据不一致问题,解决方案是「延迟双删」,但这个延迟时间很难评估,所以推荐用「先更新数据库,再删除缓存」的方案

在「先更新数据库,再删除缓存」方案下,为了保证两步都成功执行,需配合「消息队列」或「订阅变更日志」的方案来做,本质是通过「重试」的方式保证数据一致性

在「先更新数据库,再删除缓存」方案下,「读写分离 + 主从库延迟」也会导致缓存和数据库不一致,缓解此问题的方案是「延迟双删」,凭借经验发送「延迟消息」到队列中,延迟删除缓存,同时也要控制主从库延迟,尽可能降低不一致发生的概率。

掌握缓存和数据库一致性问题,核心问题有 3 点:缓存利用率、并发、缓存 + 数据库一起成功问题。

对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。

就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。

如果不能容忍缓存数据不一致,可以通过加分布式读写锁保证并发读写或写写的时候按顺序排好队,读读的 时候相当于无锁。

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

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

相关文章

双十一限时优惠!沃通SSL证书、代码签名证书年度好价

2023年11月01日至11月11日,沃通2023“双十一限时特惠”活动,精选9款SSL证书、国密SSL证书、代码签名证书产品推出年度好价,部分SSL证书产品低至5折,更有EV代码签名证书爆款特惠!多种数字证书一站式采购,解决…

外汇天眼:GOMAX──假网友热心教投资,高返利活动骗入金

在通讯科技如此发达的今日,人们愈来愈习惯透过网路交友,寻找志同道合的伙伴,甚至发展一段亲密关系。 然而,近年来假交友诈骗十分猖獗,至今已造成许多民众极大的财务损失,成为无法忽视的社会问题。 不久前&a…

听GPT 讲Rust源代码--library/std(6)

题图来自 Why you should use Python and Rust together[1] File: rust/library/std/src/sys/unix/thread_parking/netbsd.rs 文件netbsd.rs位于Rust源代码的rust/library/std/src/sys/unix/thread_parking目录下。该文件是Rust标准库中用于Unix操作系统的线程等待和唤醒机制的…

基于单片机的智能电子鼻的设计

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 技术交流认准下方 CSDN 官方提供的联系方式 文章目录 概要 一、智能电子鼻系统的设计方案1.1智能电子鼻系统的设计思路1.2智能电子鼻系统的设计流程图1.3智能电子鼻系统的硬件数…

leetcode第369周赛

2917. 找出数组中的 K-or 值 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 nums 中的 K-or 是一个满足以下条件的非负整数: 只有在 nums 中,至少存在 k 个元素的第 i 位值为 1 ,那么 K-or 中的第 i 位的值才是 1 。 返回 nums …

4.1 Bloom算法

一、Bloom算法介绍 1.具体效果 2.实现思路 先将原图按照一定阈值提取较亮的区域模糊提取出的图像将模糊过的图像与原图混合 3.HDR与LDR LDR(Low Dynamic Range,低动态范围) JPG、PNG格式图片RGB范围在[0,1]之间 HDR(High Dynam…

计算机网络第3章-TCP协议(2)

TCP拥塞控制 TCP拥塞控制的三种方式: 慢启动、拥塞避免、快速恢复 慢启动 当一条TCP连接开始时,cwnd的值是一个很小的MSS值,这使得初始发送速率大约为MSS/RTT。 在慢启动状态,cwnd的值以1个MSS开始并且每当传输的报文段首次被…

使用Hystrix实现请求合并,降低服务器并发压力

1.引入Hystrix <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency> 2.在启动类上开启Hystrix功能 EnableHystrix 3.请求合并实现代码 import com…

解决Linux Debian12系统中安装VirtualBox虚拟机无法使用USB设备的问题

Debian12系统中安装VirtualBox&#xff0c;再VirtualBox虚拟机中无法使用 USB设备。如下图所示&#xff1a; 解决方法如下&#xff1a; 1.安装 Virtualbox增强功能。如下图所示&#xff1a; 2.添加相关用户、用户组&#xff08; Virtualbox 装完成后会有 vboxusers 和 vboxs…

Linux 网络驱动实验(PHY芯片LAN8720)

目录 嵌入式网络简介嵌入式下的网络硬件接口MII/RMII 接口MDIO 接口RJ45 接口I.MX6ULL ENET 接口简介 PHY 芯片详解PHY 基础知识简介LAN8720A 详解SR8201F 详解 Linux 内核网络驱动框架net_device 结构体net_device_ops 结构体sk_buff 结构体网络NAPI 处理机制 I.MX6ULL 网络驱…

SQL SERVER 表分区

1. 概要说明 SQL SERVER的表分区功能是为了将一个大表&#xff08;表中含有非常多条数据&#xff09;的数据根据某条件&#xff08;仅限该表的主键&#xff09;拆分成多个文件存放&#xff0c;以提高查询数据时的效率。创建表分区的主要步骤是 1、确定需要以哪一个字段作为分…

vite vue3 ts 使用sass 设置样式变量 和重置默认样式

1.安装scss 样式支持依赖 yarn add -D sass 2.使用sass <div><!-- 测试使用sass --><h1>测试使用sass</h1> </div><style scope lang"scss"> div {h1 {color: red;} } </style> 效果&#xff1a; 3.通过npm下载并复制…

Spring Cloud之Gateway网关学习【详细】

目录 统一网关Gateway 网关的实现 搭建网关 编写配置文件 路由断言工程 路由的过滤器 全局过滤器 网关过滤器执行顺序 网关的cors跨域配置 问题及解决 统一网关Gateway 网关的实现 SpringCloud中存在两种网关 gateway&#xff1a;基于Spring5中提供的WebFlux实现&a…

Unity 粒子特效-第三集-星星闪烁特效

一、特效预览 二、制作原理 星星素材资源 链接&#xff1a;https://pan.baidu.com/s/17D-9sC-ErtqmUxl81Ln1Mw?pwdndm9 提取码&#xff1a;ndm9 1.素材介绍 仔细看&#xff0c;我们的粒子贴图是&#xff08;如下&#xff09;&#xff0c;一颗星星 2.步骤介绍 1.星星动画的…

【如何写论文】硕博学位论文的结构框架、过程与大纲分析

硕士论文可以说是毕业前最重要的一部分&#xff0c;也可以说是展示和检验你3年研究生学习的成果的一个考试。硕士论文答辩和检验合格&#xff0c;才能够顺利拿到毕业生和学位证&#xff0c;可见其重要性。 目录 一、基础框架1.1、摘要&#xff08;Abstract&#xff09;1.2、绪论…

【多线程面试题十五】、synchronized可以修饰静态方法和静态代码块吗?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;synchronized可以修饰静…

灯串上亚马逊加拿大合规标准CSA认证如何办理?

灯串 灯串和配件都是插头连接的便携式、临时性商品&#xff0c;最大额定输入电压为 120 伏。 本政策适用于季节性照明、装饰性灯具以及灯串。 亚马逊灯串政策 根据亚马逊的要求&#xff0c;所有季节性和装饰性灯串均应经过检测&#xff0c;并且遵守下列法规、标准和要求&…

AUTOSAR CAN协议栈架构总览介绍

Classic AUTOSAR层级架构简介 如下图是Classic AUTOSAR层级架构图,每个层主要功能如下 微控制器抽象层:使上层软件和微处理器型号无关,包含MCU中内部外设的驱动以及MCU内存映射的外部设备的驱动ECU抽象层:使上层软件和ECU硬件设计无关,包含ECU板上外部设备的驱动以及内部…

Hi3516DV500部署paddle版型分析模型记录

原版模型测试并导出onnx paddle 版面分析-> https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.7/ppstructure/layout/README_ch.md 测试 python3 deploy/python/infer.py \ --model_dirmodel/picodet_lcnet_x1_0_fgd_layout_cdla_infer/ \ --image_fil…

Apache Dolphinscheduler如何不重启解决Master服务死循环

个人建议 Apache Dolphinscheduler作为一个开源的调度平台&#xff0c;目前已经更新到了3.X版本&#xff0c;4.0版本也已经呼之欲出。3.0版本作为尝鲜版本&#xff0c;新添加了许多的功能&#xff0c;同时也存在非常多的隐患&#xff0c;本人使用3.0版本作为生产调度也踩了很多…