【Redis】所以延迟双删有啥用

news2024/11/24 19:38:55

文章目录

    • 1、何为延时双删
    • 2、常用缓存策略
      • 2.1、介绍
      • 2.2、先删缓存后更库
      • 2.3、先更库后删缓存
      • 2.4、使用场景
    • 3、延时双删实现
    • 4、为什么要使用延时双删
    • 5、方案选择
    • 6、延时双删真的完美吗
    • 7、如何确定延时的时间

1、何为延时双删

延迟双删(Delay Double Delete)是一种在数据更新或删除时为了保证数据一致性而采取的策略。这种策略通常用于解决数据在缓存和数据库中不一致的问题

具体来说,在某些场景下,我们需要先更新或删除数据库中的数据,然后再更新或删除缓存中的数据,以保证数据的一致性。但在某些情况下,由于网络延迟、服务器故障或其他原因,可能导致缓存中的数据更新或删除失败,从而导致数据库和缓存中的数据不一致

值得注意的是,不管哪种方案,都避免不了Redis存在脏数据的问题,只能减轻这个问题,要想彻底解决,得要用到同步锁和对应的业务逻辑层面解决。

2、常用缓存策略

2.1、介绍

这里只提及Cache Aside(旁路缓存)策略,这是我们操作Redis时最常用的一个策略,在该策略中应用程序直接与「数据库、缓存」交互,并负责对缓存的维护,该策略又可以细分为「读策略」和「写策略」。

写策略:先更新数据库中的数据,再删除缓存中的数据。

读策略

  • 如果读取的数据命中了缓存,则直接返回数据;
  • 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。

img

注意,写策略的步骤的顺序不能倒过来,即不能先删除缓存再更新数据库,原因是在「读+写」并发的时候,会出现缓存和数据库的数据不一致性的问题。

2.2、先删缓存后更库

前面提及到写策略的步骤的顺序不能倒过来,即不能先删除缓存再更新数据库,这里举一个例子演示:

  • 假设某个用户的年龄是20,请求A更新用户年龄为21,所以它会删除缓存中的内容。

  • 这时,另一个请求B读取这个用户的年龄,它查询缓存发现未命中后,会从数据库中读取到年龄为20,并且写回到缓存中。

  • 请求A继续更改数据库,将用户的年龄更新为21。

  • 最终,该用户年龄在缓存中是20(旧值),在数据库中是21(新值),缓存和数据库的数据不一致

img

2.3、先更库后删缓存

那么**「先更新数据库再删除缓存」一定不会有数据不一致的问题吗**?继续用「读 + 写」请求的并发的场景来分析:

  • 假如某个用户数据在缓存中不存在请求A读取数据时从数据库中查询到年龄为 20。
  • 在未写入缓存中时另一个请求B更新数据。请求B更新数据库中的年龄为 21,并且清空缓存
  • 这时请求A把从数据库中读到的年龄为20的数据写入到缓存中。
  • 最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库数据不一致

img

从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。这主要有以下原因:

  • 缓存的写入通常要远远快于数据库的写入
  • 所以在实际中很难出现请求B已经更新了数据库并且删除了缓存请求A更新完缓存的情况。
  • 而一旦请求A早于请求B删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以一般不会出现这种不一致的情况。

2.4、使用场景

Cache Aside 策略适合读多写少的场景,不适合写多的场景,因为当写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响。如果业务对缓存命中率有严格的要求,那么可以考虑两种解决方案:

  • 一种做法是在更新数据时也更新缓存,只是在更新缓存前先加一个分布式锁。因为这样在同一时间只允许一个线程更新缓存,就不会产生并发问题了。当然这么做对于写入的性能会有一些影响;
  • 另一种做法同样也是在更新数据时更新缓存,只是给缓存加一个较短的过期时间。这样即使出现缓存不一致的情况,缓存的数据也会很快过期,对业务的影响也是可以接受。

3、延时双删实现

在前面介绍到,先更新数据库后删Redis缓存是一致性相对最高的。这是就有人举手了:我就想要先删缓存怎么办?这时延时双删就出现了,针对「先删除缓存,再更新数据库」方案在「读 + 写」并发请求而造成缓存不一致的解决办法是「延迟双删」。

延迟双删实现的伪代码如下:

#删除缓存
redis.delKey(X)
#更新数据库
db.update(X)
#睡眠
Thread.sleep(N)
#再删除缓存
redis.delKey(X)

这里做一个详细介绍:

  1. 首先,代码先删除了 Redis 中的缓存数据,以确保接下来的读取操作会从数据库中读取最新的数据。
  2. 接着,代码更新了数据库中的数据,将数据更新为最新的值。
  3. 在此之后,代码让当前线程休眠一段时间N,这个时间段是为了给数据库操作足够的时间来完成,确保数据已经持久化到数据库中。
  4. 最后,代码再次删除 Redis 中的缓存数据。这里是延迟双删的关键步骤。由于之前已经删除了缓存数据,再次删除的目的是为了防止在 Thread.sleep(N) 的时间内有其他线程读取到旧的缓存数据。因为在这段时间内,缓存数据已经被清空,所以其他线程在读取数据时会发现缓存中不存在,然后从数据库中读取最新的数据并写入缓存,从而保证了数据的一致性。

需要注意的是,这种延迟双删策略并不能完全保证数据的一致性。

如果在 Thread.sleep(N) 的时间内发生了其他线程的写入操作,并且将新数据写入了缓存中,那么在第二次删除缓存时,会将这个新数据从缓存中删除,可能导致缓存和数据库中的数据不一致。

因此,延迟双删策略只能在一定程度上提高数据一致性的概率,但不能完全解决数据一致性的问题。更加严格的数据一致性保证需要使用更复杂的机制,比如使用消息队列等。

4、为什么要使用延时双删

在延时双删策略中,当需要更新数据库中的数据时,首先会先删除缓存,然后再进行数据库的更新操作。这样做的目的是为了避免在数据库更新的过程中,有其他请求读取了已经失效的缓存数据

通过延时双删策略,可以保证在数据库更新期间,其他读取请求在缓存不命中的情况下,会直接读取数据库的最新数据,而不会读取到已经失效的缓存数据。这样就保证了数据的一致性和缓存的即时更新。

延时双删策略虽然会增加一次缓存删除的开销,但是可以有效地提高数据的一致性,并且在高并发读取的场景下,减轻数据库的读取压力,提高读取性能和响应速度。

5、方案选择

对于先删除缓存后更新数据库这种方案,由于出现数据不一致性的可能性偏高,数据库读写压力偏大以及性能偏低,因此这一方案一般不予与考虑,这里主要对延时双删方案先更新数据库后删除缓存方案进行分析。

针对于前面的介绍,可以分析出以下结论:

  • 延时双删适用于对数据一致性要求较高的场景。它能够保证在数据库更新期间,读取请求不会读取到已经失效的缓存数据,从而保证数据的一致性。但是它需要进行两次缓存删除操作,可能会增加一定的资源开销;
  • 先更新数据库后删除缓存适用于对一致性要求较低,对性能要求较高的场景。它能够减少一次缓存删除的开销,但是在数据库更新期间,读取请求可能会读取到已经失效的缓存数据,从而导致数据不一致。

同时,还可以根据实际情况做一些权衡和优化。比如可以使用读写锁来减少数据库更新期间的并发读取请求,从而降低数据不一致的可能性。或者可以考虑使用更高效的缓存淘汰算法,来降低缓存的过期时间,减少缓存失效的影响。

方案优点缺点实现复杂度适用场景
先更新数据库后删除缓存减少了一次缓存删除的开销在数据库更新期间,读取请求可能读取到失效的缓存数据简单数据一致性要求较低、对性能要求较高的场景
延时双删保证了数据一致性,读取请求不会读取到失效的缓存数据需要进行两次缓存删除操作,增加了一定的资源开销复杂数据一致性要求较高的场景,同时对性能影响有一定容忍度的场景

6、延时双删真的完美吗

在认识到这个方案的时候,我就冒出了这么一种疑问不知道大家有没有:

为什么要执行第一次删除缓存的操作呢?留着缓存不是也能缓解数据库并发读取的压力吗?执行第一次删除缓存的操作还会多花费一定的资源去执行删除操作。

为了解决这一个问题我也去查询了许多资料和博文吸取经验,这个问题也是得到了一定的解决,如果有错误希望大伙热情提出。

首先,**为什么要执行第一次删除缓存的操作?**这是因为在并发环境下,如果直接更新数据库而不删除缓存,会导致脏数据问题。考虑以下场景:

  1. 线程A读取缓存中的旧数据。
  2. 线程B更新数据库中的数据,并删除缓存。
  3. 线程A继续使用缓存中的旧数据,因为此时它不知道缓存已经被删除。

为了避免这种脏数据问题,需要在更新数据库之前,先删除缓存,这样其他读取请求会从数据库中读取最新数据。

接着说为什么需要延迟再次删除缓存。延迟再次删除缓存的目的是为了在数据库更新期间,保留旧数据的缓存,以缓解数据库并发读取的压力。在延迟时间内,其他读取请求会从缓存中读取旧数据,而不会直接读取数据库。

虽然执行第一次删除缓存的操作会带来一定的资源开销,但通过合理设置延迟时间和优化缓存策略,可以在高并发读取场景下,有效降低对数据库的直接读取次数,从而提高读取性能和并发性能。这样在一段时间内,仍能从缓存中获取数据,减少数据库压力,而在数据库更新完成后,再次删除缓存以确保最终的数据一致性。

这么看来是有那么一点点脱裤子放屁的感觉哈,我刚开始也是有这么一种感觉的,但是一切都要以实际场景来决定的。

第一次删除缓存的操作是为了以一定的资源开销为代价,让缓存中的旧数据在一定时间内相对较新,以便在数据库更新期间,其他读取请求可以从缓存中获取旧数据,从而减轻对数据库的直接读取压力。这有些类似于写锁,在更新数据库时,尽可能的保证写之前的数据是最新的,但只是尽可能,虽然大部分保证了,但是还是会有一定的可能会出现脏数据问题。

这样做的目的是为了在高并发读取场景下提高性能,通过缓存中的旧数据,避免大量读取请求直接访问数据库,降低数据库的并发读取压力。同时,因为缓存的更新是延迟进行的,所以在一定时间内,读取请求会持续从缓存中获取数据,而不会频繁访问数据库,从而提高了读取性能和响应速度。

在第一次删除缓存到更新数据库期间,请求压力其实是由数据库服务和Redis服务两者一起承担的。当请求缓存不命中时,请求会打到数据库查询数据并写回缓存,之后请求压力将会由Redis服务承担。

7、如何确定延时的时间

确定延时双删中延时的时间是一个需要根据实际场景和需求来进行权衡的过程。延时的时间需要根据数据库的更新操作耗时、缓存的过期时间以及应用的实际负载情况,通过不断的测试来确定。

如果数据库的更新操作通常很快,可以选择较短的延时时间,比如几百毫秒或一秒钟。这样可以尽快地更新缓存,减少读取请求的直接访问数据库的次数,提高缓存的读取性能。

如果数据库的更新操作较为耗时,可能需要选择较长的延时时间,比如几秒钟或更长。这样可以保证数据库的更新操作完成后再删除缓存,避免读取请求获取到过期的缓存数据,保证数据一致性。

另外,延时的时间还需要考虑缓存的过期时间。如果缓存的过期时间较长,可以适当缩短延时的时间;如果缓存的过期时间较短,可以适当延长延时的时间,以免过早地删除缓存导致数据不一致。

对于还需要考虑缓存的过期时间原因如下:

假设在延时双删策略中,第一次删除缓存后,会有一段时间的延时,然后再进行第二次删除缓存。如果此时缓存的过期时间设置得很短,比如只有几秒钟,那么在第二次删除缓存之前,缓存可能已经过期,而应用程序在读取缓存时会发现缓存已失效,从而不得不去数据库中查询最新数据。

为了避免这种情况,延时双删的延时时长应该要大于缓存的过期时间,确保在第二次删除缓存之前,缓存还是有效的,这样可以保证应用程序读取到的数据是一致的。

同时还需要考虑数据更新的频率和缓存的使用情况。如果数据更新较为频繁,那么延时双删的延时时长应该要适当缩短,以便及时更新缓存;如果缓存的使用率很低,可以适当延长延时时长,以减少对缓存服务的压力。

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

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

相关文章

Jupyter 安装、简单操作及工作路径更换

一、Jupyter下载安装 pip install jupyterAnaconda是Python另一个非常流行的发行版,它之后有着自己的叫做“conda”的安装工具。用户可以使用它来安装很多第三方包。然而,Anaconda会预装很多包,包括了Jupyter Notebook,所以若已经安装了Anac…

VUE3 语法教程

vue3 起步 刚开始学习 Vue,我们不推荐使用 vue-cli 命令行工具来创建项目,更简单的方式是直接在页面引入 vue.global.js 文件来测试学习。 Vue3 中的应用是通过使用 createApp 函数来创建的,语法格式如下: const app Vue.crea…

cmder 使用简介

文章目录 1. cmder 简介2. 下载地址3. 安装4. 配置环境变量5. 添加 cmder 到右键菜单6. 解决中文乱码问题 1. cmder 简介 cmder 是一个增强型命令行工具,不仅可以使用 windows 下的所有命令,更爽的是可以使用 linux的命令, shell 命令。 2. 下载地址 …

机器学习深度学习——预备知识(下)

机器学习&&深度学习——预备知识(下) 4 微积分4.1 导数和微分4.2 偏导数4.3 梯度4.4 链式法则 5 自动微分5.1 简单例子5.2 非标量变量的反向传播5.3 分离计算5.4 Python控制流的梯度计算 6 概率6.1 基本概率论6.1.1 概率论公理 6.2 处理多个随机…

5.1 Bootstrap 插件概览

文章目录 Bootstrap 插件概览data 属性编程方式的 API避免命名空间冲突事件 Bootstrap 插件概览 在前面 布局组件 章节中所讨论到的组件仅仅是个开始。Bootstrap 自带 12 种 jQuery 插件,扩展了功能,可以给站点添加更多的互动。即使您不是一名高级的 Jav…

超参数优化 - 贝叶斯优化的实现

目录 1. 基于Bayes_opt实现GP优化 1.1 定义目标函数 1.2 定义参数空间 1.3 定义优化目标函数的具体流程 4. 定义验证函数(非必须) 5. 执行实际优化流程 2. 基于HyperOpt实现TPE优化 2.1 定义目标函数 2.2 定义参数空间 2.3 定义优化目标函数的…

Asp.Net WebForm ViewState

ViewState用于保存服务器控件(runat"server")状态便于在触发回调后&#xff08;PostBack&#xff09;恢复页面。 页面拖入一个Form id"form1" runat"server",里面添加一个<asp:Button id"Button1" Text"删除" OnClick&q…

python:对 GEDI 数据进行高斯滤波处理

作者:CSDN @ _养乐多_ 在本篇博客中,我们将学习如何使用 Python 对 GEDI(Global Ecosystem Dynamics Investigation)激光雷达数据进行高斯滤波处理。高斯滤波是一种平滑滤波方法,可以有效减少噪声和突变,提高数据的平滑性和连续性。我们将使用 pandas 和 scipy.signal 库…

STM32MP157驱动开发——LED 驱动( GPIO 子系统)

文章目录 编写思路GPIO子系统的LED驱动程序(stm32mp157)如何找到引脚功能和配置信息在设备树中添加 Pinctrl 信息leddrv.cledtest.cMakefile编译测试 编写思路 阅读&#xff1a;STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念可知利用GPIO子系统去编写LED驱动&#x…

Mysql基础(下)之函数,约束,多表查询,事务

&#x1f442; 回到夏天&#xff08;我多想回到那个夏天&#xff09; - 傲七爷/小田音乐社 - 单曲 - 网易云音乐 截图自 劈里啪啦 -- 黑马Mysql&#xff0c;仅学习使用 &#x1f447;原地址 47. 基础-多表查询-表子查询_哔哩哔哩_bilibili 目录 &#x1f982;函数 &#x1f3…

Spark(35):Structured Streaming 概述

目录 0. 相关文章链接 1. 什么是Structured Streaming 2. Structure Streaming 快速入门 2.1. 导入依赖 2.2. 代码实现 2.3. 程序测试 2.4. 代码说明 0. 相关文章链接 Spark文章汇总 1. 什么是Structured Streaming 从 spark2.0 开始, spark 引入了一套新的流式计算模…

新Viewport单位

本文为翻译本文译者为 360 奇舞团前端开发工程师原文标题&#xff1a;New Viewport Units原文作者&#xff1a;Ahmad Shadeed原文地址&#xff1a;https://ishadeed.com/article/new-viewport-units/ 自 2012 年以来&#xff0c;我们一直在使用 CSS viewport 单位。它们对于帮助…

RGB简单人脸活体检测(Liveness Detection)

参考&#xff1a; https://github.com/minivision-ai/Silent-Face-Anti-Spoofing&#xff08;主要这个库&#xff09; https://github.com/computervisioneng/face-attendance-system&#xff08;使用案例&#xff09; ##概念&#xff1a; 活体检测是指针对人脸识别过程中的人脸…

TSDB - VictoriaMetrics 技术原理浅析

一、前言 在监控领域&#xff0c;通常需要指标存储组件TSDB&#xff0c;目前开源的TSDB组件比较多&#xff0c;各个组件性能、高可用性、维护成本等等各有差异。本文不分析选型问题&#xff0c;重点讲解VictoriaMetrics&#xff08;后面简称为vm&#xff09;。 有兴趣的朋友建议…

Linux中常用的一些shell命令

很多的时候我们知道有一个命令&#xff0c;但不知道它的详细用法&#xff0c;可以来搜索下。但有些时候压根不知道有这个命令&#xff0c;比如vimdiff和diff这两个命令&#xff0c;知道人就比较少。 本节内容主要汇总一下Linux中常用的一些shell命令。 1. 文件和目录操作 ls …

win11安装MySQL5.7.43的问题清单

文章目录 1、win11查看自己电脑有没有安装mysql法1法2 2、完全清除之前安装的mysql3、 mysql的安装法1法2 4、遇到的一些问题1) ‘mysql‘不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件2) 忘记mysql的密码3)mysql启动不了:本地计算机上的MySQL服务启动后停止4…

机器学习深度学习——torch.nn模块

机器学习&&深度学习——torch.nn模块 卷积层池化层激活函数循环层全连接层 torch.nn模块包含着torch已经准备好的层&#xff0c;方便使用者调用构建网络。 卷积层 卷积就是输入和卷积核之间的内积运算&#xff0c;如下图&#xff1a; 容易发现&#xff0c;卷积神经网…

汽车养护店服务难题,看帕凝怎样解决?

中国汽车市场庞大&#xff0c;入户已然成为标配&#xff0c;加之新能源汽车近些年高增量&#xff0c;更促进了行业增长。而汽车后市场也迎来了一系列变化&#xff0c;客户服务前后路径需完善&#xff0c;商家们应该如何数字化经营呢&#xff1f; 接下来让我们看看【帕凝汽车养…

提升内功之模拟实现库函数atoi

本文包含知识点&#xff1a; 库函数atoi的使用和模拟实现枚举常量的运用fgets代替gets函数读取字符串isspace isdigit库函数的使用 一、库函数atoi的介绍与使用atoi的介绍atoi的使用细节 二、库函数atoi的模拟实现 一、库函数atoi的介绍与使用 atoi的介绍 函数介绍 头文件——…

密码学学习笔记(十七 ):Edwards曲线数字签名算法 - edDSA

Edwards曲线数字签名算法(Edwards-curve Digital Signature Alogorithm, edDSA)由Daniel J. Bernstein等人在2011年提出&#xff0c;它是一种使用基于扭曲爱德华兹曲线的Schnorr签名变体的数字签名方案。 EdDSA的一个特殊之处在于&#xff0c;该方案不要求每次签名都是用全新的…