Redis缓存双写一致性之更新策略

news2025/1/15 20:07:39

Redis缓存双写一致性之更新策略

  • 一 面试题引入
  • 二 缓存双写一致性
  • 三 双写双检加锁策略
  • 四 数据库和缓存一致性的集中更新策略
    • 4.1 最终一致性
    • 4.2 可以关机的情况下
    • 4.3 不能关机的情况下,四种更新策略
      • 4.3.1 先更新数据库,再更新缓存
      • 4.3.2 先更新缓存,再更新数据库
      • 4.3.3 先删除缓存,再更新数据库
      • 4.3.4 先更新数据库,再删除缓存(主流)--实现最终一致性
  • 五 总结
    • 5.1 如何选择方案?利弊如何?

一 面试题引入

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

二 缓存双写一致性

  • 如果redis中有数据:需要和数据库中的值相同
  • 如果redis中无数据:数据库中的值要是最新值,且准备回写redis
  • 缓存按照操作来分,细分2种
    • 只读缓存:
    • 读写缓存:

      同步直写策略:

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

      异步缓写策略:

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

三 双写双检加锁策略

在这里插入图片描述
当高并发的情况下,采用双检加锁策略:
多个线程同时去查询数据库的这条记录,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

public String get(String key){
	String value = redis.get(key);//查询缓存
	if(value != null){
		//缓存存在直接返回
		return value;
	}else{
		//缓存不存在则对方法加锁
		//加锁请求量很大,缓存过期(对于高QPS的优化)
		synchronized(TestFuture.class){
			value = redis.get(key);//再查一遍redis
			if(value != null){
				//查到数据直接返回
				return value;
			}else{
				//二次查询缓存也不存在,直接查DB
				value = dao.get(key);
				//数据缓存
				redis.setnx(key,value,time);
				//返回
				return value;
			}
		}
	}
}

四 数据库和缓存一致性的集中更新策略

4.1 最终一致性

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

4.2 可以关机的情况下

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

4.3 不能关机的情况下,四种更新策略

4.3.1 先更新数据库,再更新缓存

  • 更新redis失败,导致数据库和缓存redis里面数据不一致,读到redis脏数据。
  • 先更新数据库,再更新缓存,A/B两个线程发起调用。

    异常情况下:多线程环境下,A/B两个线程有快有慢,导致写入mysql顺序与更新redis顺序不一致,使两端数据不一致。

4.3.2 先更新缓存,再更新数据库

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

异常情况下:多线程环境下,A/B两个线程有快有慢,导致写入redis顺序与更新mysql顺序不一致,使两端数据不一致。

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

  • 异常情况

    • A线程先成功删除了redis里面的数据,然后去更新mysql,此时mysql正在更新中,还没有结束(比如网络延迟),B线程突然出现要读取缓存数据。
    • 此时redis里面的数据时空的,B线程来读取,此时有2个问题:
      • B从mysql中获取到了旧值:B线程发现redis里没有(缓存缺失)马上去mysql里面读取,从数据库里面读取来的是旧值
      • B会把获取到的旧值写回redis:获得旧值数据后返回前台并回写进redis(刚被A线程删除的旧数据有极大可能又被写回了)
    • A线程更新完mysql,发现redis里面的缓存是脏数据,
  • 解决方案:采用延时双删策略在这里插入图片描述
    加上sleep的这段时间,就是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓存。然后,线程A再进行删除。所以,线程A sleep的时间,就需要大于线程B读取数据再写入缓存的时间。这样一来,其它线程读取数据时,会发现缓存缺失,所以会从数据库中读取最新值。因为这个方案会在第一次删除缓存值后,延迟一段时间再次进行删除,所以我们也把它叫做“延迟双删”。

  • 延时双删策略问题引入:

    • 这个删除该休眠多久呢?

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

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

      在这里插入图片描述

4.3.4 先更新数据库,再删除缓存(主流)–实现最终一致性

  • 异常问题:假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中,读取到的是缓存旧值。
  • 解决方案:在这里插入图片描述
  1. 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用Kafla/RabbitMQ等)。
  2. 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或者更新。
  3. 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的一致性,否则还需要再次进行重试。
  4. 如果重试超过了一定次数后还没有成功,我们就需要向业务层发送报错信息了,通知运维人员。

五 总结

5.1 如何选择方案?利弊如何?

优先使用先更新数据库,再删除缓存的方案(先更库–>后删库),理由如下:

  1. 先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力导致打满mysql。
  2. 如果业务应用中读取数据库和写缓存的时间不好估算,那么延迟双删中的等待时间就不好设置。

在这里插入图片描述

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

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

相关文章

【算法学习系列】03 - 由[1-5]等概率随机实现[2-10]等概率随机

文章目录 约定条件说明解决方案构造 0 1 发生器函数 f2()计算需要几个二进制位验证 2-10 等概率返回某个整数 总结 约定条件说明 假定 f() 是一个函数,保证 [1, 5] 范围内等概率返回一个整数实现 2-10 等概率随机不能使用 Math.random() 函数,只能使用函…

栈与队列的性质互换

本期内容:栈,队列的定义性质,性质转换 栈,队列的定义性质,性质转换 认识栈实现栈 队列实现 性质转换 认识栈 栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和…

【渗透测试】web日志、linux命令、常用知识

文章目录 web日志分析基础知识1. 编码2. 解码工具3. 数据提交方式4. 常见脚本语言5. 日志还原 分析日志1. 分析日志的目的2. 攻击出现的位置3. 攻击常见的语句4. 攻击常见的特点5. 攻击日志分析流程 相关linux命令常用命令系统状态检测命令工作目录切换命令文本文件编辑命令文件…

BlueZ自动连接蓝牙耳机

问题:调好蓝牙之后,出现了一个客户问题,第一次连接好之后,开关机后没法自动连了。 解决方法: 针对这个情况,实际定位一下问题原因,原来是蓝牙耳机每次连时,都要求授权服务: Author…

sqlmap

1、Sqlmap简介: Sqlmap是一个开源的渗透测试工具,可以用来自动化的检测,利用SQL注入漏洞,获取数据库服务器的权限。它具有功能强大的检测引擎,针对各种不同类型数据库的渗透测试的功能选项,包括获取数据库…

Maven安装和配置(详细版)

Maven安装和配置 Maven安装1、安装链接:2、配置环境变量: Maven配置1、修改Maven仓库下载镜像及修改仓库位置:2、在Idea上配置Maven: 测试Maven安装能否安装jar包 Maven安装 1、安装链接: Maven – Download Apache …

使用A100 GPU搭建OBBDetection的运行环境

项目场景: 最近需要复现一篇目标检测论文的代码,文章提供了代码,因此自己根据仓库的说明尝试配置环境运行代码,但遇到了非常多的困难 问题描述 比较老的代码加上比较的GPU,导致了环境在配置的时候困难重重 OBBDetect…

xorm多表连接查询

SQL的连接查询可以将多个表的数据查询出来,形成一个中间表。在sql中为JOIN关键字。最常用的是LEFT JOIN,RIGHT JOIN,INNER JOIN,OUTER JOIN。 xorm框架是基于go语言的orm框架同样支持连接查询,由于xom及支持原生的sql查询也支持基于xorm的方法查询&…

openEuler用户软件仓(EUR)| 近期项目介绍

在操作系统的世界,软件包是一等公民,软件包的丰富程度和是否易于分发,一定程度上决定了操作系统用户和开发者的使用体验.。 EUR(openEuler User Repo)是openEuler社区针对开发者推出的个人软件包托管平台,目的在于为开发者提供一个…

【LeetCode训练营】用栈来实现队列+用队列来实现栈 详解

💯 博客内容:【LeetCode训练营】用栈来实现队列用队列来实现栈 详解 😀 作  者:陈大大陈 🚀 个人简介:一个正在努力学技术的准前端,专注基础和实战分享 ,欢迎私信! …

Requests-翻页请求实现

翻页请求实现 继https://blog.csdn.net/ssslq/article/details/130747686之后,本篇详述在获取了页面第一页之后,如何获取剩余页的标题内容。 网页:https://books.toscrape.com 找规律 同样还是进行页面的检查,切到网络一栏&…

MySQL查询——joininunion

MySql多表查询的几种方法 连接查询——join自连接查询子查询——🛠in合并查询——Union 认识MySQL数据库的多表查询,在对大量数据进行查询时仅仅使用一些基本的SQL语句已经无法满足我们日益增长的需求,如果要对多表进行查询就不得不认识以下几…

【计算机网络基础】测试2 物理层

文章目录 判断题选择题辨析题应用题 判断题 现在的无线局域网常用的频段是2.8GHz和5.4GHz。 多模光纤只适合于近距离传输。√ 数据在计算机内部多采用串行传输方式,但在通信线路上多采用并行传输方式。 统计时分复用可以按需动态分配时隙。√ 相对于同步时分复用…

安装Ubuntu系统

## ubuntu 22.04 环境处理(按顺序安装) 1. 搜索并打开“windows 功能”窗口 勾上图示的2项,点确定安装,可能要求重启电脑 2. windows store 中 安装ubuntu 22.04 wsl 一定要登录Microsoft Store 账号再操作 3.在ubuntu安装node.j…

以SpringMVC入门案例分析服务器初始化过程、单次请求流程

文章目录 1,SpringMVC概述2,SpringMVC入门案例2.1 需求分析2.2 案例制作步骤1:创建Maven项目步骤2:补全目录结构步骤3:导入jar包步骤4:创建配置类步骤5:创建Controller类步骤6:使用配置类替换web.xml步骤7:配置Tomcat环境步骤8:启动运行项目步骤9:浏览器…

RabbitMQ --- 死信交换机(一)

前言 当我们在使用消息队列时,难免会遇到一些消息被拒绝,重复投递或者超时等异常情况。这些异常消息如果不被正确处理,将会阻碍整个消息系统的正常运行。而此时,死信交换机(Dead Letter Exchange,简称DLX&…

【遇到dfs问题,点进来看看思路】【dfs本质之一 全排列思想解决大部分dfs】例题1.全排列 例题2.单词接龙

总之就是 在已知格线上,填充可用数据, 如果回退到A,那么把A之前所用数据,换一个,并且A之后的数据都重新填写 这就是全排列(截取的最关键部分,往下看) 这样的话,就是dfs的…

【节点边际电价】机组运行约束对机组节点边际电价的影响分析(Matlab代码实现)​

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

2023年美国大学生数学建模竞赛F题绿色GDP解题全过程文档及程序

2023年美国大学生数学建模竞赛 F题 绿色GDP 原题再现: 背景   国内生产总值(GDP)可以说是衡量-一个国家经济健康状况的最知名和最常用的指标之一。它通常被用于确定一个国家的购买力和获得贷款的机会,为国家提出促进其gdp的政策和项目提供动力。GDP衡…

Springboot +Flowable,流程表单应用之外置表单(HTML形式)(一)

一.简介 整体上来说,我们可以将Flowable 的表单分为三种不同的类型: 动态表单 这种表单定义方式我们可以配置表单中每一个字段的可读性、可写性、是否必填等信息,不过不能定义完整的表单页面。外置表单 外置表单我们只需要定义一下表单的 k…