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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

开发指南002-前后端信息交互规范-概述

前后端之间采用restful接口,服务和服务之间使用feign。信息交互遵循如下平台规范: 前端: 建立api目录,按照业务区分建立不同的.js文件,封装对后台的调用操作。其中qlm*.js为平台预制的接口文件,以qlm_user.…

【红外与可见光融合:条件学习:实例归一化(IN)】

Infrared and visible image fusion based on a two-stage class conditioned auto-encoder network (基于两级类条件自编码器网络的红外与可见光图像融合) 现有的基于自动编码器的红外和可见光图像融合方法通常利用共享编码器从不同模态中提取特征&am…

arduino安装索尼spresense开发库

arduino安装索尼spresense开发库 一.库安装二.库文件下载1.直接下载2.git下载1.git加速下载2.git下载加速3.将文件导入arduino 一.库安装 打开arduino点击文件->首选项 将以下链接添加进附加开发板管理器网址 https://github.com/sonydevworld/spresense-arduino-compatib…

什么是数据采集与监视控制系统(SCADA)?

SCADA数据采集是一种用于监控和控制工业过程的系统。它可以实时从现场设备获得数据并将其传输到中央计算机,以便进行监控和控制。SCADA数据采集系统通常使用传感器、仪表和控制器收集各种类型的数据,例如温度、压力、流量等,然后将这些数据汇…

【李沐】动手学习ai思路softmax回归实现

来源:https://www.cnblogs.com/blzm742624643/p/15079086.html 一、从零开始实现 1.1 首先引入Fashion-MNIST数据集 1 import torch 2 from IPython import display 3 from d2l import torch as d2l 4 5 batch_size 256 6 train_iter, test_iter d2l.load_data…

tcp流式服务和粘包问题

目录 1.概念 2.流式服务 3.粘包问题 1.概念 套接字是一个全双工的 使用TCP协议通信的双方必须先建立连接,然后才能开始数据的读写,双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输. TCP连接是全双工的,即双方的数据读写可以通过一个连接进行,完成…

集合框架(一)List系列集合

特点 有序,可重复,有索引。 LIst集合的特有方法 /** 目标:掌握List系列集合的特点,以及其提供的特有方法* */import java.util.ArrayList; import java.util.List;public class ListTest1 {public static void main(String[] arg…

android开发环境搭建

android开发环境搭建 Android 开发环境搭建1.JDK安装与配置1.1 Jdk官方下载1.2 JDK安装1.3 环境变量配置1.4 新建JAVA_HOME1.5 修改Path变量1.6 新建classpath1.7 验证环境是否配置完成 2.开发工具二选一1.如何创建一个工程2.工程的目录结构的了解3.与开发的相关的常规视图4.我…

记录WiFi转WDS桥接再转网线

第一步: 把LAN口修改为 和 主路由器的前三位段位编码一致,最后一位设置大于250,减少抢IP的可能性。这个步骤是修改 桥接路由器的登录IP 第二部: 设置IP池。网关和dns服务器都是同一个,用手机连接主路由器wifi可以找到 …

【Flink】Flink 的八种分区策略(源码解读)

Flink 的八种分区策略(源码解读) 1.继承关系图1.1 接口:ChannelSelector1.2 抽象类:StreamPartitioner1.3 继承关系图 2.分区策略2.1 GlobalPartitioner2.2 ShufflePartitioner2.3 BroadcastPartitioner2.4 RebalancePartitioner2…

HTML 学习笔记(五)超链接

HYperText 超文是用超链接的方式&#xff0c;将不同空间的文字信息组合在一起的网状文其就像一个桥梁&#xff0c;建立了不同页面中的联系&#xff0c;实现了访问不同网站中页面的功能 <!DOCTYPE html> <html lang"en"><head><meta charset&qu…

深度学习+感知机

深度学习感知机 1感知机总结 2多层感知机1XOR2激活函数3多类分类总结 3代码实现 1感知机 是个很简单的模型,是个二分类的问题。 感知机&#xff08;perceptron&#xff09;是Frank Rosenblatt在1957年提出的一种人工神经网络&#xff0c;被视为一种最简单形式的前馈神经网络&…

【C语言】深入理解指针(进阶篇)

一、数组名的理解 数组名就是地址&#xff0c;而且是数组首元素的地址。 任务&#xff1a;运行以下代码&#xff0c;看数组名是否是地址。 #include <stdio.h> int main() {int arr[] { 1,2,3,4,5,6,7,8,9,0 };printf("&arr[0] %p\n", &arr[0]);pri…

[Java安全入门]三.CC1链

1.前言 Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库&#xff0c;它提供了很多强大的数据结构类型和实现了各种集合工具类。Commons Collections触发反序列化漏洞构造的链叫做cc链&#xff0c;构造方式多种&#xff0c;这里先学习cc1链…

Dubbo-记录

1.概念 Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力&#xff0c; 利用 Dubbo 提供的丰富服务治理…

C语言--函数指针变量和函数指针数组的区别(详解)

函数指针变量 函数指针变量的作用 函数指针变量是指向函数的指针&#xff0c;它可以用来存储函数的地址&#xff0c;并且可以通过该指针调用相应的函数。函数指针变量的作用主要有以下几个方面&#xff1a; 回调函数&#xff1a;函数指针变量可以作为参数传递给其他函数&…

【数仓】通过Flume+kafka采集日志数据存储到Hadoop

相关文章 【数仓】基本概念、知识普及、核心技术【数仓】数据分层概念以及相关逻辑【数仓】Hadoop软件安装及使用&#xff08;集群配置&#xff09;【数仓】Hadoop集群配置常用参数说明【数仓】zookeeper软件安装及集群配置【数仓】kafka软件安装及集群配置【数仓】flume软件安…

【深度学习笔记】6_10 双向循环神经网络bi-rnn

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.10 双向循环神经网络 之前介绍的循环神经网络模型都是假设当前时间步是由前面的较早时间步的序列决定的&#xff0c;因此它们都将信…

【Leetcode打卡】递归回溯

【Leetcode打卡】递归回溯 784. 字母大小写全排列 class Solution { public:int find(string s,int pos){int ipos;while(i<s.size()){if(isalpha(s[i])){return i;}i;}return -1;}void turn(string& s,int pos){if(islower(s[pos])){s[pos]toupper(s[pos]);}else{s[po…

ChatGPT提示词工程:prompt和chatbot

ChatGPT Prompt Engineering for Developers 本文是 https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/ 这门课程的学习笔记。 ChatGPT提示词工程&#xff1a;prompt和chatbot 文章目录 ChatGPT Prompt Engineering for DevelopersWhat …