Redis作为缓存的数据一致性问题

背景

使用Reids作为缓存的原因:
在高并发场景下,传统关系型数据库的并发能力相对比较薄弱(QPS不能太大);
使用Redis做一个缓存。让用户请求先打到Redis上而不是直接打到数据库上。
但是如果出现数据更新操作:数据库与缓存更新,就会出现缓存(Redis)和数据库(MySQL)之间的数据一致性问题。

非读写分离架构:延时双删

先更新数据库,再更新缓存,为什么不可行?

线程安全角度

同时有请求A、B进行更新操作

执行顺序如下:
线程A更新了数据库
线程B更新了数据库
线程B更新了缓存
线程A更新了缓存

缓存和数据库出现了不一致

业务角度

如果某个业务场景是写多读少,就会导致缓存并未被读取就会被频繁的更新,极大的浪费了服务器的性能。
因为数据库的值,并不是直接刷入缓存,有的业务需要经过一系列复杂的计算再写入缓存。

先删除缓存,再更新数据库,为什么不可行?

线程安全角度

请求A进行写操作,先删除缓存
请求B查询发现缓存不存在
请求B去数据库查询得到旧值
请求B将旧值写入缓存
请求A更新数据库

此时数据库中的值是新值,缓存的值是旧值,就发生了数据不一致问题

延时双删

线程 A:
当应用程序需要更新数据时,首先将数据更新到数据库
A 线程向 Redis 发送删除缓存的指令,将缓存标记为过期
A 线程等待一定的时间窗口(通常是几十ms~几百ms),让 B 线程有足够的时间去访问缓存

线程 B:
在时间窗口内,当有请求访问过期的缓存数据时,B 线程发现缓存已过期,并触发缓存更新的操作
B 线程从数据库中获取到最新数据,并将其存储到缓存中
B 线程返回更新后的缓存数据

线程 A(续):
在时间窗口结束后,A 线程再次向 Redis 发送删除缓存的指令,彻底删除缓存数据
如果在时间窗口内没有请求访问到过期的缓存数据,A 线程会删除已标记为过期的缓存数据

通过上述流程
A 线程负责标记缓存过期并等待一段时间,给 B 线程足够的时间去访问缓存并更新
B 线程则负责处理实际的缓存更新操作
这样即使在缓存更新期间有请求访问过期的缓存数据,也能获取到最新的数据,避免脏读

注意
具体的时间窗口大小和线程的实现方式可以根据实际需求和系统性能进行调整。
同时,对于高并发环境,还需要考虑线程安全和并发控制的实现,以确保操作的正确性和性能。

读写分离架构(有专门的读服务、专门的写服务,写主,读从主)

可以采用先更新数据库,再删除缓存,配合上重试机制

问题

两个请求:请求A进行更新操作,请求B进行查询操作
请求A进行写操作,删除缓存
请求A将数据写入数据库,
请求B查询缓存发现,缓存没有值
请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值
请求B将旧值写入缓存
数据库完成主从同步,从库变为新值
仍然会出现缓存与数据库数据不一致问题

延时双删的问题

延时时间需要在主从同步的延时时间基础上,加几百ms

双删失败
如果第二次删除缓存失败,仍然会出现缓存与数据库数据不一致的问题

同样还是有两个请求请求A进行更新操作,请求B进行查询操作(单库)
请求A进行写操作,删除缓存
请求B查询发现缓存不存在
请求B去数据库查询得到旧值
请求B将旧值写入缓存
请求A将新值写入数据库
请求A试图去删除请求B写入的缓存值,结果失败了

删除失败的重试补偿机制

先更新数据库,再删除缓存
同样存在并发问题,但是发生几率很低

两个请求:请求A进行更新操作,请求B进行查询操作(单库)
缓存刚好失效
请求A查询数据库,得一个旧值
请求B将新值写入数据库
请求B删除缓存
请求A将查到的旧值写入缓存

该情况发生的必要条件就是请求B写数据库的操作比请求A读数据库的操作耗时更短,才能使请求B先删除缓存
但是通常来说数据库的读操作是远远快于写操作的,所以这种并发问题很难发生。

如果在极端情况下,这种并发问题仍然发生了
给缓存设置一定的有效时间

异步延时双删策略

另起一个线程,异步删除,保证读请求完成以后,再进行删除操作

重试机制
与先删除缓存,再更新数据一样,如果删除缓存失败,那么仍然会出现数据不一致问题

选择靠谱的重试机制,比如利用消息队列进行删除的补偿

方案一:

更新数据库数据;
缓存因为种种问题删除失败
将需要删除的key发送至消息队列
自己消费消息,获得需要删除的key
继续重试删除操作,直到成功

在这里插入图片描述
缺点
对业务线代码造成大量的侵入,需要在业务代码中额外添加生成消息和消费消息的功能
业务代码变得不再专注于业务需求。

改进:
启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据
在应用程序中,另起一段程序(避免业务侵入),获得这个订阅程序传来的信息,进行删除缓存操作

方案二

更新数据库数据
数据库会将操作信息写入binlog日志当中
订阅程序提取出所需要的数据以及key
另起一段非业务代码,获得该信息
尝试删除缓存操作,发现删除失败
将这些信息发送至消息队列

在这里插入图片描述
重新从消息队列中获得该数据,重试操作订阅binlog程序在mysql中有现成的中间件叫canal
可以完成订阅binlog日志的功能

附录

MySQL的查询QPS主要取决于硬件性能以及应用的查询优化,通常在千到万的范围
但在特定的配置下可以达到几万到十几万。

通常来说,对于大多数应用场景而言,MySQL的QPS在2000-3000就已经比较高了。

过高的QPS可能会对服务器性能产生负面影响,如CPU和I/O压力过大。
因此,最佳的QPS应根据实际的硬件配置和应用需求来定。

通过对查询的优化,如合理的索引设计、合理的查询设计,也能够有效提高QPS
通过使用缓存、读写分离、分库分表等方式,也能显著提高系统的并发处理能力,从而提高QPS

词汇

写入失败重试直到成功,称之为:删除补偿

学习文档

https://mp.weixin.qq.com/s?__biz=Mzg5MjE0MjE3Mw==&mid=2247488095&idx=1&sn=c4f50e3dbfd381c3f9c6948b973f8063&chksm=cfc3c56df8b44c7be1a7284a0f6b9255274fb50e3cefcf98e7e6897b1df0f8080223012a312b#rd

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

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

相关文章

springboot256基于springboot+vue的游戏交易系统

游戏交易系统设计与实现 摘 要 在如今社会上,关于信息上面的处理,没有任何一个企业或者个人会忽视,如何让信息急速传递,并且归档储存查询,采用之前的纸张记录模式已经不符合当前使用要求了。所以,对游戏交…

Vue快速开发一个主页

前言 这里讲述我们如何快速利用Vue脚手架快速搭建一个主页。 页面布局 el-container / el-header / el-aside / el-main&#xff1a;https://element.eleme.cn/#/zh-CN/component/container <el-container><el-header style"background-color: #4c535a"…

使用GitOps自动化推动AI/ML工作流程

作为一名深耕自动化和人工智能领域的开发人员&#xff0c;我们逐渐认识到尖端工具和方法之间的显着协同作用&#xff0c;这些协同作用突破了可能性的界限。在这次探索中&#xff0c;我们想分享一个概念&#xff0c;它不仅彻底改变了我们的软件开发和基础设施管理方法&#xff0…

VRRP与BFD在项目中的结合使用

学习目标&#xff1a; 1. VRRP双网关热备份怎样部署&#xff1f; 2. BFD是一种怎样的检测技术&#xff1f; 3. VRRP与BFD联动实现故障的快速切换&#xff1b; 虚拟一个192.168.1.1的网关&#xff1a; 虚拟路由器冗余协议&#xff1a;VRRP 人为调节角色选举 流量分担是可以的&…

【C/C++】常量指针与指针常量的深入解析与区分(什么是const int * 与 int * const ?)

目录 一、前言 二、const 的简单介绍 三、常量指针 &#x1f50d;介绍与分析 &#x1f4f0;小结与记忆口诀 四、指针常量 &#x1f50d;介绍与分析 &#x1f4f0;小结与记忆口诀 五、总结与提炼 六、共勉 一、前言 在【C/C】的编程中&#xff0c;指针与const关键字的组合…

阿珊详解Vue Router的守卫机制

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

回溯算法09-子集II(Java/子集问题的去重方法)

9.子集II 题目描述 给你一个整数数组 nums &#xff0c;其中可能包含重复元素&#xff0c;请你返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。返回的解集中&#xff0c;子集可以按 任意顺序 排列。 示例 1&#xff1a; 输入&#xf…

Angular基础---HelloWorld---Day2

文章目录 1.循环语句&#xff1a; *ngfor2.循环语句&#xff1a;ngSwitch4.事件的绑定:click5.事件的绑定:input6.模版引用变量7.数据双向绑定ngModel8.动态表单控件9.动态表单空间组 文末附有代码仓库地址&#xff01;&#xff01;&#xff01; 1.循环语句&#xff1a; *ngfor…

Qt自定义控件

自定义控件 目的&#xff1a;将多个控件或者窗口作为一个整体被多次复用。 操作方式 1.首先进行自定义的ui设计&#xff0c;以及对应的.h和.cpp文件 2.到要使用的UI界面上&#xff0c;从控件库中拖拽一个Widget控件 3.右键点击"提升为" 4.填写自定义实现的类名&…

指纹加密U盘/指纹KEY方案——采用金融级安全芯片 ACH512

方案概述 指纹加密U盘解决方案可实现指纹算法处理、数据安全加密、数据高速存取&#xff08;EMMC/TF卡/NandFlash&#xff09;&#xff0c;可有效保护用户数据安全。 方案特点 • 采用金融级安全芯片 ACH512 • 存储介质&#xff1a;EMMC、TF卡、NandFlash • 支持全系列国密…

Web Worker:JavaScript的后台任务解决方案

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

程序如何知道mqtt设备是否在线

在做物联网设备的时候经常会碰到设备的在线与掉线 问题&#xff1a;emqx如何来实现这个在线与掉线 实现&#xff1a;添加一个规则&#xff0c;程序监控这个规则 1、SELECT * FROM "$events/client_connected", "$events/client_disconnected" 2、添加一…