Redis缓存双写一致性笔记(上)

news2024/11/18 11:48:15

Redis缓存双写一致性是指在将数据同时写入缓存(如Redis)和数据库(如MySQL)时,确保两者中的数据保持一致性。在分布式系统中,缓存通常用于提高数据读取的速度和减轻数据库的压力。然而,当数据更新时,如果没有适当的机制来同步缓存和数据库,可能会导致用户读到的数据是过时的或不一致的。

1.缓存双写一致性的理解

如果redis中有数据,需要和数据库中的值相同

如果redis中无数据, 数据库中的值要是最新值,且准备回写redis

缓存按照操作来分,细分以下2种

  • 只读缓存

  • 读写缓存

    • 同步直写策略:写数据库之后也同步写redis缓存,缓存和数据库中的数据一致;

      对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步直写策略

    • 异步缓写策略:正常业务中,MySQL数据变了,但是可以在业务上容许出现一定时间后才作用于redis,比如仓库、物流系统;异常情况出现了, 不得不将失败的动作重新修补,有可能需要借助kafka或者RabbitMQ等消息中间件,实现重试重写

采用双检加锁策略

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

如果数据库里没有,缓存空数据,避免大量请求发生缓存穿透

2.数据库和缓存一致性的更新策略

目的:达到数据最终一致性

   给缓存设置过期时间,定期清理缓存并回写,是保证最终一致性的解决方案。 我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性,切记,要以mysql的数据库写入库为准

上述方案和后续落地案例是调研后的主流+成熟的做法,但是考虑到各个公司业务系统的差距,不是100%绝对正确,不保证绝对适配全部情况,需要自己酌情选择打法,合适自己的最好。

可以停机的情况

挂牌报错,凌晨升级,温馨提示,服务降级

单线程,这样重量级的数据操作最好不要多线程

我们讨论4种更新策略

2.1 先更新数据库,在更新缓存(不可取)

异常问题1

  • 先更新mysql的某商品的库存,当前商品的库存是100,更新为99个。
  • 先更新mysql修改为99成功,然后更新redis。
  • 此时假设异常出现,更新redis失败了,这导致mysql里面的库存是99而redis里面的还是100。
  • 上述发生,会让数据库里面和缓存redis里面数据不一致,读到redis脏数据

异常问题2

【先更新数据库,再更新缓存】﹐A、B两个线程发起调用
【正常逻辑】
1 A update mysql 100
2 A update redis 100
3 B update mysql 80
4 B update redis 80
=============================
【异常逻辑】
多线程环境下,A、B两个线程有快有慢,有前有后有并行
1 A update mysql 100
3 B update mysql 80
4 B update redis 80
2 A update redis 100
=============================
最终结果,mysql和lredis数据不一致
mysql:80,redis:100

2.2 先更新缓存,再更新数据库(不可取)

不推荐,业务上一般把MySQL作为底单数据库 ,保证最后解释

[先更新缓存,再更新数据库],A、B两个线程发起调用
[正常逻辑]
1 A update redis 100
2 A update mysql 100
3 B update redis 80
4 B update mysql 80
====================================
[异常逻辑]多线程环境下,A. B两个线程有快有慢有并行
A update redis 100
B update redis 80
B update mysq| 80
A update mysql 100
====================================
mysql:100,redis:80

2.3 先删除缓存,在更新数据库(不可取)

异常问题:

步骤分析,先删除缓存,再更新数据库

1 A线程先成功删除了redis里面的数据,然后去更新mysql,此时mysql正在更新中,还没有结束。(比如网络延时)
B突然出现要来读取缓存数据。

2 此时redis里面的数据是空的,B线程来读取,先去读redis里数据(已经被A线程delete掉了),此处出来2个问题:
2.1 B从mysq|获得了旧值
B线程发现redis里没有(缓存缺失)马上去mysql里面读取,从数据库里面读取来的是旧值。
2.2 B会把获得的旧值写回redis
获得旧值数据后返回前台并回写进redis(刚被A线程删除的旧数据有极大可能早被写回了)。

3 A线程更新完mysql,发现redis里面的缓存是脏数据,A线程直接懵逼了,o(T_ .τ)o

两个并发操作,一个是更新操作,另一个是查询操作,A删除缓存后,B查询操作没有命中缓存,B先把老数据读出来后放到缓存中,然后A更新操作更新了数据库。
于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。
4总结流程:
(1)请求A进行写操作,删除redis缓存后,工作正在进行中,更新mysql.....
A还么有彻底更新完mysql,还没commit
(2)请求B开工查询,查询redis发现缓存不存在(被A从redis中删除了)
(3)请求B继续,去数据库查询得到了mysq中的旧值(A还没有更新完)
(4)请求B将旧值写回redis缓存
(5)请求A将新值写入mysql数据库
上述情况就会导致不一致的情形出现。

先删除缓存,再更新数据库:如果数据库更新失败或超时或返回不及时,导致B线程请求访问缓存时发现redis里面没数据,缓存缺失,B再去读取mysql时,从数据库中读取到旧值,还写回redis, 导致A白干了

解决方案:采用延时双删策略 

加上sleep的这段时间,就是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓存,然后,线程A再进行删除。所以,线程A sleep的时间,就需要大于线程B读取数据再写入缓存的时间。这样一来,其它线程读取数据时,会发现缓存缺失,所以会从数据库中读取最新值。因为这个方案会在第一次删除缓存值后,延迟一段时间再次进行删除,所以我们也把它叫做“延迟双删”。

延迟双删面试题

这个删除该休眠多久呢?线程A sleep的时间,就需要大于线程B读取数据再写入缓存的时间。

这个时间怎么确定呢? 第一种方法: 在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可。 这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。 第二种方法: 新启动一个后台监控程序,比如后面要讲解的WatchDog监控程序,会加时

这种同步淘汰策略,吞吐量降低怎么办?

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

异常问题

时间线程A线程B出现的问题
t1更新数据库中的值......
t2缓存立刻命中,此时B读取的是缓存旧值A还没来得及删除缓存的值,导致B缓存命中读到旧值
t3更新缓存的数据,over

先更新数据库,在删除缓存,假如缓存删除失败或者来不及删除,导致请求再次访问redis时缓存命中,读取到的是缓存的旧值。

解决方案 :

  • 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用Kafka/RabbitMQ等)。
  • 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
  • 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的数据一致了,否则还需要再次进行重试 4 如果重试超过的一定次数后还是没有成功,我们就需要向业务层发送报错信息了,通知运维人员。

3. 双写一致总结

方案如何选择?利弊如何

在大多数业务场景下, 个人建议是,优先使用先更新数据库,再删除缓存的方案(先更库→后删存)。理由如下:

1先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力导致打满mysql。

2如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删中的等待时间就不好设置。

多补充一句:如果使用先更新数据库,再删除缓存的方案)

如果业务层要求必须读取一致性的数据,那么我们就需要在更新数据库时,先在Redis缓存客户端暂停并发读请求,等数据库更新完、缓存值删除后,再读取数据,从而保证数据一致性,这是理论可以达到的效果,但实际,不推荐,因为真实生产环境中,分布式下很难做到实时一致性,一般都是最终一致性。

策略高并发多线程条件下问题现象解决方案
先删除redis缓存,再更新mysql缓存删除成功但数据库更新失败Java程序从数据库中读到旧值再次更新数据库,重试
缓存删除成功但数据库更新中... 有并发请求并发请求从数据库读到旧值并回写到redis,导致后续都是从redis读取到旧值再次删除缓存,重试
先更新mysql,再删除redis缓存数据库更新成功,但缓存删除失败Java程序从redis中读到旧值再次删除缓存,重试
数据库更新成功但缓存删除中...... 有并发读请求并发请求从缓存读到旧值等待redis删除完成,这段时间数据不一致,短暂存在。

4.最后

祝愿大家国庆快乐!

感谢大家,请大家多多支持!

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

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

相关文章

NetAssist测试TCP和UDP

由于在Windows下经常使用NetAssist.exe这款网络调试工具进行TCP、UDP的服务端、客户端的监听,对于需要编写各种通信协议的TCP服务端、客户端以及UDP通信程序来说是很方便的。下载地址:http://free.cmsoft.cn/download/cmsoft/assistant/netassist5.0.14.…

Docker容器的使用

前提条件 Linux环境安装好Docker,可参考Rocky Linux9下安装Docker和卸载Docker Docker命令图 帮助命令 帮助命令,查看有哪些命令可以用 [rootlocalhost ~]# docker --help ​ 查看某个命令的帮助,例如:run [rootlocalhost ~]# …

MySQL递归查询笔记

目录 一、创建表结构和插入数据 二、查询所有子节点 三、查询所有父节点 四、查询指定节点的根节点 五、查询所有兄弟节点(同级节点) 六、获取祖先节点及其所有子节点 七、查询每个节点之间的层级关系 八、查询指定节点之间的层级关系 一、创建表…

一款辅助渗透测试过程,让渗透测试报告一键生成

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

动态顺序表的增删改查(数据结构)

目录 一、顺序表 二、静态顺序表 三、动态顺序表 3.1、动态顺序表的实现 3.2、动态顺序表的实现 3.3.1、结构体创建 3.3.2、初始化 3.3.3、销毁数据 3.3.4、增容空间 3.3.5、尾插数据 3.3.6、头插数据 3.3.7、删除尾数据 3.3.8、打印数据 3.3.9、删除头数据 3.3…

设备管理系统-TPM(PC+APP/PDA全流程)高保真Axure原型 源文件分享

随着科技的不断发展,企业对于设备管理的需求也日益增强。为了满足企业在设备管理方面的各种需求,站长为大家整理了一套设备管理系统TPM(PCAPP/PDA全流程)高保真Axure原型,通过这套原型,企业能够实现对设备的…

TDSQL-C电商小助手,AI驱动的智能化数据洞察新纪元

前言: 在数字经济蓬勃发展的今天,电商行业作为数字化转型的先锋,正以前所未有的速度积累着海量数据。这些数据不仅是交易记录的简单堆砌,更是企业洞察市场趋势、优化运营策略、提升用户体验的宝贵财富。然而,如何从这…

【2024.9.28练习】青蛙的约会

题目描述 题目分析 由于两只青蛙都在跳跃导致变量多,不妨采用物理题中的相对运动思想,设青蛙A不动,青蛙B每次跳米,两只青蛙的距离为米。正常来说,只要模拟青蛙B与青蛙A的相对运动过程,最终当青蛙B与青蛙A距…

Goweb---Gorm操作Mysql数据库(一)

本文重点: db.AutoMigrate()这个函数的理解: AutoMigrate是GORM提供的一个方法,用于自动迁移你的模型(即数据库表结构)到数据库中,确保数据库表与你的Go结构体(模型)保持一致。 首先…

第十四届蓝桥杯真题Python c组D.平均(持续更新)

博客主页:音符犹如代码系列专栏:蓝桥杯关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ 【问题描述】 有一个长度为n的数组(n是10的倍数),每个数 …

vscode配置Eslint后保存出现大量波浪线

解决问题:配置代码格式化 快捷键打开设置:ctrlshiftP 输入: format code 选择:

MySQL-数据库约束

1.约束类型 类型说明NOT NULL非空约束 指定非空约束的列不能存储NULL值 DEFAULT默认约束当没有给列赋值时使用的默认值UNIQUE唯一约束指定唯一约束的列每行数据必须有唯一的值PRIMARY KEY主键约束NOT NULL和UNIQUE的结合,可以指定一个列霍多个列,有助于…

qt6 使用QPSQL

qt6 自带pg数据库驱动: pro文件加个说明: 引用位置添加(按需添加,这里我就大致加一下): test code: 理想情况当然是要用pool,这里只是演示调用而已 QSqlError DbTool::testConnection(const QString &…

LSTM预测未来30天销售额

加入深度实战社区:www.zzgcz.com,免费学习所有深度学习实战项目。 1. 项目简介 本项目旨在利用深度学习中的长短期记忆网络(LSTM)来预测未来30天的销售额。LSTM模型能够处理时序数据中的长期依赖问题,因此在销售额预测这类涉及时…

19款奔驰E300升级新款触摸屏人机交互系统

《19 款奔驰 E300 的科技焕新之旅》 在汽车科技日新月异的时代,19 款奔驰 E300 的车主们为了追求更卓越的驾驶体验,纷纷选择对爱车进行升级改装,其中新款触摸屏人机交互系统的改装成为了热门之选。 19 款奔驰 E300 作为一款经典车型&#x…

Vue和axios零基础学习

Vue的配置与项目创建 在这之前要先安装nodejs 安装脚手架 官网 Home | Vue CLI (vuejs.org) 先运行,切换成淘宝镜像源,安装速度更快 npm config set registry http://registry.npm.taobao.org 创建项目 用编译器打开一个空文件,在终端输入…

DMA的原理

一、介绍 DMA(Direct Memory Access)是一种允许设备直接与内存进行数据交换的技术,无需‌CPU干预。DMA的主要功能是提供在‌外设和存储器之间或者存储器和存储器之间的高速数据传输。比如使用ADC进行数据采集,可以直接将数据存入…

【STM32】江科大STM32笔记汇总(已完结)

STM32江科大笔记汇总 STM32学习笔记课程简介(01)STM32简介(02)软件安装(03)新建工程(04)GPIO输出(05)LED闪烁& LED流水灯& 蜂鸣器(06)GPIO输入(07)按键控制LED 光敏传感器控制蜂鸣器(08)OLED调试工具(09)OLED显示屏(10)EXTI外部中断(11)对射式红外传感器计次 旋转编码器…

GAMES101(21~22节,动画和仿真)

Animation 关键帧 动画和几何(曲线)相关 物理模拟/仿真 牛顿第二定律:F ma 需要清楚网格间相互作用力,也需要把物理仿真和渲染分为两部来看,例如布料模拟,流体模拟 mass spring system质点弹簧系统 …

Nest.js实现一个简单的聊天室

本文将介绍如何使用 Nest.js 和 Uni-app 实现一个简单的实时聊天应用。后端使用 nestjs/websockets 和 socket.io,前端使用 uni-app 并集成 socket.io-client。这个项目允许多个用户同时加入聊天并实时交换消息。 效果图: 一、准备工作 安装 Node.js 和…