当前位置: 首页 > news >正文

Redis高级篇之缓存一致性详细教程

文章目录

  • 0 前言
  • 1.缓存双写一致性的理解
    • 1.1 缓存按照操作来分
  • 2. 数据库和缓存一致性的几种更新策略
    • 2.1 可以停机的情况
    • 2.2 我们讨论4种更新策略
    • 2.3 解决方案
  • 总结

0 前言

  缓存一致性问题在工作中绝对没办法回避的问题,比如:在实际开发过程中,通常添加把权限菜单存在缓存中,而用户登录成功以后获取的都是缓存中的权限菜单,当发现用户没有权限,想要添加时,已经添加上了,但是用户却查不出该权限,这说明添加只保存在数据中,并没有同步数据到缓存中,这就是本章节要讨论的缓存双写死一致性问题。
  而在找工作面试时,或遇到的问题如下:

  • 你只要用缓存,就可能涉及到redis缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
  • 双写一致性,你先动缓存redis还是数据库MySQL哪一个?why?
  • 延时删除你做过吗?会有哪些问题?
  • 有这么一种情况,微服务查询redis无 MySQL有,为保证数据双写一致性回写redis你需要注意什么?双检加锁策略你了解过吗?如何尽量避免缓存击穿?
  • redis和MySQL双写100%会出纰漏,做不到强一致性,你如何保证最终一致性?

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

  如下图所示,数据库中,缓存一致性问题,简单的说就是,数据库中的数据和缓存中的数据保持一致性。通常在开发时,查找数据是,先找缓存,如果缓存没有数据则查找数据库。
  查找流程一共分为三个步骤:
  1.缓存里有数据,直接返回
  2.缓存里无数据,查找数据库。
  3.从数据库中查找数据后,数据回写Redis,保持数据两边一致。
  其中,Redis挡在前面起到保护数据库的作用。因为数据库支持的并发量和Redis支持的并发量不是一个等级的。至于Redis为什么能够支持那么多的并发量,可去看看我之前写过的相关文章。Redis高阶篇之Redis单线程与多线程
在这里插入图片描述
  总之简单一句话,如果redis中有数据,​ 需要和数据库中的值相同。如果redis中无数据,​ 数据库中的值要是最新值,且准备回写redis。

1.1 缓存按照操作来分

  1.只读缓存
  2.读写缓存

  • 同步直写策略
    写数据库之后也同步写redis缓存,缓存和数据库中的数据一致;
    对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步直写策略
  • 异步缓写策略
    正常业务中,MySQL数据变了,但是可以在业务上容许出现一定时间后才作用于redis,比如仓库、物流系统
    异常情况出现了, 不得不将失败的动作重新修补,有可能需要借助kafka或者RabbitMQ等消息中间件,实现重试重写
  • 采用双检加锁策略
    多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。具体代码如下所示,仅供参考。
public User findUserById(Integer id){User user = null ;String key = CACHE_KEY_USER+iduser = redisTemplate.opsForValue.get(key);if(user  == null){// 2.高并发场景使用,进来先加锁,保证一个请求操作,让外面的线程等待,避免击穿数据库。synchronized(UserService.class){user = redisTemplate.opsForValue.get(key);if(user ==null){user = userMapper.selectByPrimaryKey(id);if(user == null){//3.1  redis和数据库  都无数据// 你具体细化,防止多次穿透,我们业务规定,记录一下这个null值的key,			   列入黑名单或者记录异常return user;}else{// 3.2 数据库里有数据,需要将数据回写到redis,保证下一次命中redisTemplate.opsForValue.setIfAbsent(key,user,7L,TimeUnit.DAYS);}}}}return user;
}

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

  基本准则:总之,我们要达到最终一致性的目的。
  给缓存设置过期时间,定期清理缓存并回写,是保证最终一致性的解决方案。
  我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性,切记,要以mysql的数据库写入库为准。
  上述方案和后续落地案例是调研后的主流+成熟的做法,但是考虑到各个公司业务系统的差距,不是100%绝对正确,不保证绝对适配全部情况,需要自己酌情选择打法,合适自己的最好。

2.1 可以停机的情况

  挂牌报错,凌晨升级,温馨提示,服务降级;
  单线程,这样重量级的数据操作最好不要多线程;

2.2 我们讨论4种更新策略

  1. 先更新数据库,在更新缓存,本文中在高并发的系统下不建议使用。原因是线程的执行顺序快慢先后顺序问题,造成了缓存存了脏数据。具体案例问题描述如下:
异常问题1

1 先更新mysql的某商品的库存,当前商品的库存是100,更新为99个。
2 先更新mysql修改为99成功,然后更新redis。
3. 此时假设异常出现,更新redis失败了,这导致mysql里面的库存是99而redis里面的还是100。
4.上述发生,会让数据库里面和缓存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数据不一致,o(T_T)o, mysql80,redis100

  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两个线程有快有慢有并行
1 A update redis 100
3 B update redis 80
2 B update mysq| 80
4 A update mysql 100
mysql 100,redis 80

  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白干了。

  4.先更新数据库,再删除缓存。
  目前用的比较多,但是也会有异常情况。异常问题如下表格所示:
在这里插入图片描述
  先更新数据库,在删除缓存,假如缓存删除失败或者来不及删除,导致请求再次访问redis时缓存命中,读取到的是缓存的旧值。

2.3 解决方案

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

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

  • 业务指导思想
    微软云:点击链接跳转
    后面的阿里巴巴canal也是类似的思想
    订阅binlog程序在MySQL中有现成的中间件叫canal,可以完成订阅binlog日志的功能。

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

  • 类似经典的分布式事务问题,只有一个权威答案,只能达到最终一致性。
    流量充值,先下发短信实际充值可能滞后5分钟,可以接受。
    电商发货,短信下发但是物流明天见。

总结

  方案如何选择?利弊如何
  在大多数业务场景下, 个人建议是,优先使用先更新数据库,再删除缓存的方案(先更库→后删存)。理由如下:
  1.先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力导致打满mysql。
  2.如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删中的等待时间就不好设置。
  多补充一句:如果使用先更新数据库,再删除缓存的方案
  如果业务层要求必须读取一致性的数据,那么我们就需要在更新数据库时,先在Redis缓存客户端暂停并发读请求,等数据库更新完、缓存值删除后,再读取数据,从而保证数据一致性,这是理论可以达到的效果,但实际,不推荐,因为真实生产环境中,分布式下很难做到实时一致性,一般都是最终一致性。
在这里插入图片描述
  ,本篇只介绍了缓存数据一致性问题,下一篇就可以到数据一致性的落地了。

相关文章:

Redis高级篇之缓存一致性详细教程

文章目录 0 前言1.缓存双写一致性的理解1.1 缓存按照操作来分 2. 数据库和缓存一致性的几种更新策略2.1 可以停机的情况2.2 我们讨论4种更新策略2.3 解决方案 总结 0 前言 缓存一致性问题在工作中绝对没办法回避的问题,比如:在实际开发过程中&#xff0c…...

C++ 文件操作详解

C 文件操作详解 在C中,文件操作分为文本文件和二进制文件的操作,通过文件流类(ifstream、ofstream、fstream)进行文件的读写。这些类封装了文件的输入和输出操作,并继承了istream和ostream的功能,使得流对…...

计算机网络:网络层 —— 边界网关协议 BGP

文章目录 路由选择协议动态路由协议边界网关协议 BGPBGP 的基本概念BGP-4 的四种报文 路由选择协议 因特网是全球最大的互联网,它所采取的路由选择协议具有以下三个主要特点: 自适应:因特网采用动态路由选择,能较好地适应网络状态…...

Python数据可视化seaborn

产品经理在做数据分析时可能需要通过可视化来分析。seaborn官网 1. relplot 散点图 https://seaborn.pydata.org/examples/scatterplot_sizes.html import pandas as pd import seaborn as sns df pd.DataFrame({x: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],y: [8, 6, 7, 8, 4, 6,…...

Java继承练习

构建Person类(属性:名字、年龄、工作岗位),创建三个对象,并且根据对象的年龄或名字长度来进行冒泡排序 package chapter08.homework.test01;public class homework01 {public static void main(String[] args) {Perso…...

Excel怎么转换成word?分享两种方法!

Excel文件想要转换成word文档中,直接粘贴复制的话,可能会导致表格格式错乱,那么如何转换才能够保证表格不错乱?今天分享两个方法,excel表格转换为word文件。 方法一: 首先打开excel表格,将表格…...

nignx代理获取真实地址request.getRequestURL()

# 反向代理配置到后端接口 location /prod-api/ { # proxy_set_header Host $proxy_host; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarde…...

登录注册窗口(二)

登录注册窗口(二) 前言 在上一集我们就完成了整个登录注册窗口的布局,我们为了能够点击那两个特殊的按钮,我们就要去连接他们的槽函数。那么我们就开始这一集的内容吧。 需求分析 为了能够切换手机号与用户名的注册登录以及注…...

go channel 通道

一、底层实现 1、数据结构 type hchan struct {qcount uint // total data in the queuedataqsiz uint // size of the circular queuebuf unsafe.Pointer // points to an array of dataqsiz elementselemsize uint16closed uint32timer *t…...

论文阅读:Computational Long Exposure Mobile Photography (二)

这篇文章是谷歌发表在 2023 ACM transaction on Graphic 上的一篇文章,介绍如何在手机摄影中实现长曝光的一些拍摄效果。 Abstract 长曝光摄影能拍出令人惊叹的影像,用运动模糊来呈现场景中的移动元素。它通常有两种模式,分别产生前景模糊或…...

基于SSM+小程序的高校寻物平台管理系统(失物1)

👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 本基于微信小程序的高校寻物平台有管理员,用户以及失主三个角色。 1、管理员功能有个人中心,用户管理,失主管理,寻物启示管理,拾…...

gerrit 搭建遇到的问题

1、启动Apache,端口被占用 : AH00072: make sock: could not bind to address (0S 10048)通常每个套接字地址(协议/网络地址/端口)只允许使用一次。: AH00072: make sock: could not bind to address 0.0.0.:443 a AH00451: no listening sockets available, shutti…...

UBUNTU查看CPU核心数

UBUNTU查看CPU核心数 前言一、使用lscpu命令1. 执行命令2. 查看输出 二、使用/proc/cpuinfo文件1. 查看文件2. 解释输出 三、使用nproc命令1. 执行命令2. 查看输出 四、使用htop或top工具1. 使用htop2. 使用top 五、使用inxi命令1. 执行命令2. 查看输出 六、使用图形界面工具1.…...

【JS】声明提升与块级作用域

我是目录 引言声明提升声明提升的理解函数表达式声明提升总结代码生成与查找变量的过程代码生成词法分析( Tokenizing/Lexing)语法分析( Parsing)代码生成生成代码总结查找变量不同版本中的执行上下文不同版本对执行上下文的定义let/constlet特点const特点let/const声明的变…...

Flink的流、批处理

Flink的数据流处理,是持续流模型,数据不会落地,上游和下游的Task同时启动,等待数据的到达,Flink的批处理还是用的MapReduce计算模型,先处理map端,再执行reduce端。 flink的流处理(STREAMING)&a…...

学习方法该升级了,‌AI时代的弯道超车:【心流学习法】行动与意识合一的巅峰进化

你是否曾感到内心如荒漠般干涸,面对浩瀚的知识海洋,热情逐渐消磨殆尽? 你是否渴望忘却时间的流逝,心无旁骛,与知识展开一场纯粹而深邃的对话? ​在AI时代,智能体处理数据、知识迭代的速率让人…...

【大模型LLM面试合集】大语言模型架构_chatglm系列模型

chatglm系列模型 1.ChatGLM 1.1 背景 主流的预训练框架主要有三种: autoregressive自回归模型(AR模型):代表作GPT。本质上是一个left-to-right的语言模型。通常用于生成式任务,在长文本生成方面取得了巨大的成功&a…...

深入理解 Kafka:分布式消息队列的强大力量

一、引言 在现代分布式系统中,消息队列扮演着至关重要的角色,而 Kafka 作为其中的佼佼者,以其高吞吐量、可扩展性和持久性等特点被广泛应用。无论是处理海量的日志数据、实时的用户交互信息,还是复杂的微服务间通信,Ka…...

LabVIEW 离心泵机组故障诊断系统

开发了一套基于LabVIEW图形化编程语言设计的离心泵机组故障诊断系统。系统利用先进的数据采集技术和故障诊断方法,通过远程在线监测与分析,有效提升了离心泵的预测性维护能力,保证了石油化工生产的连续性和安全性。 项目背景及意义 离心泵作…...

GEE土地分类——土地分类的原始remap转化原始的土地分类名称

简介 GEE土地分类——土地分类的原始remap转化原始的土地分类名称 函数 first(image2) Selects the value of the first value for each matched pair of bands in image1 and image2. If either image1 or image2 has only 1 band, then it is used against all the bands…...

一些关于云电脑与虚拟化东西

前言 好久没有更新了,在进行自我校准。 云计算是什么? 云计算是一种模型,它使得用户能够随时随地、方便地、按需访问共享的可配置计算资源池(例如,网络、服务器、存储、应用程序和服务),这些资…...

Java实现图片转pdf

该方法可以选择多个图片是否合并为一个pdf输出&#xff0c;也可以选择图片为横向或者纵向输出&#xff0c;也可以选择pdf页面为A3或者A4 第一步 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version…...

【iOS】使用AFNetworking进行网络请求

文章目录 前言AFNetworkingAFNetworking 的核心组件AKNetworking库的常用方法使用AKNetworking进行网络请求的步骤和代码示例 总结 前言 在暑假写天气预报项目时&#xff0c;我们已经接触到网络请求&#xff0c;当时我们是使用URLSession类&#xff0c;即Foundation框架中用于管…...

ThingsBoard规则链节点:RPC Call Reply节点详解

引言 1. RPC Call Reply 节点简介 2. 节点配置 2.1 基本配置示例 3. 使用场景 3.1 设备控制 3.2 状态查询 3.3 命令执行 4. 实际项目中的应用 4.1 项目背景 4.2 项目需求 4.3 实现步骤 5. 总结 引言 ThingsBoard 是一个开源的物联网平台&#xff0c;提供了设备管理…...

【AI换装整合包及教程】OOTDiffusion:以AI技术引领的时尚换装革命

在当今数字化快速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正以前所未有的速度改变着我们的生活。从智能家居到自动驾驶&#xff0c;从在线教育到虚拟现实&#xff0c;AI的应用范围正在不断扩展。而在时尚领域&#xff0c;一款名为OOTDiffusion&#xff0…...

排序算法详细总结

算法 定义&#xff1a;算法是解决特定问题的明确步骤集合。算法的效率通常用时间复杂度和空间复杂度来衡量。 排序算法 定义&#xff1a;排序算法是计算机科学中用于对元素序列进行排序的一系列算法。排序算法在各种应用中都非常常见&#xff0c;从简单的数据处理到复杂的数…...

uniapp MD5加密

安装&#xff1a; npm install js-md5 -D 引入&#xff1a; import Md5 from js-md5 需求加密一个对象&#xff0c; login_form: {openId: 123456789,phone: ,scenario: 656677,phoneSessionKey: ,openIdSessionKey: ,timeStamp: , }, //10位时间戳&#xff08;秒&#xff09;…...

提升视觉回归测试体验:Cypress 插件推荐

项目介绍 在现代前端开发中&#xff0c;视觉回归测试是确保用户界面在不同版本之间保持一致性的关键步骤。然而&#xff0c;传统的视觉回归测试工具往往复杂且难以使用。为了解决这一问题&#xff0c;我们推荐一款专为 Cypress 设计的插件&#xff1a;Cypress Plugin Visual Re…...

fastbootd模式刷android固件的方法

1. fastbootd追根溯源 Google在Android 10上正式引入了动态分区机制来提升OTA的可扩展性。动态分区使能后&#xff1a;andorid系统可以在开机阶段动态地进行分区创建、分区销毁、分区大小调整等操作&#xff0c;下游厂商只需要规划好super分区的总大小&#xff0c;其内部的各个…...

基于C#实现Windows后台窗口操作与图像处理技术分析

在Windows编程中&#xff0c;操作后台窗口是一项复杂而有用的技术。它可以用来自动化用户界面测试、应用程序机器人等场景。本文将深入探讨如何在C#中绑定后台窗口、获取后台窗口界面图片&#xff0c;以及在图片中寻找指定图标并获取坐标。本技术文章结合最先进的资料与实践经验…...