数据库和缓存如何保证一致性?

📢📢📢📣📣📣

哈喽!大家好,我是【一心同学】,一位上进心十足的【Java领域博主】!😜😜😜

✨【一心同学】的写作风格:喜欢用【通俗易懂】的文笔去讲解每一个知识点,而不喜欢用【高大上】的官方陈述。

✨【一心同学】博客的领域是【面向后端技术】的学习,未来会持续更新更多的【后端技术】以及【学习心得】。

✨如果有对【后端技术】感兴趣的【小可爱】,欢迎关注一心同学】💞💞💞

❤️❤️❤️感谢各位大可爱小可爱!❤️❤️❤️ 


目录

前言

一、先更新数据库,还是先更新缓存?

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

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

🚀 小结

二、先更新数据库,还是先删除缓存?

🌵 旁路缓存策略

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

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

三、数据一致性解决方案

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

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


前言

在我们的项目开发中,为了提高数据的访问速度以及降低数据库负载,我们通常会将热点数据存储在缓存当中,像Redis缓存是基于内存的缓存系统,读取数据的速度非常快,通常可以在微秒级别内进行响应数据,但是由于缓存的存在,也同时引入了缓存与数据库的一致性问题,本文将进行解析如何保证其一致性。

本文针对数据不一致性提供四种策略进行解析,一心在这里就不卖关子了,先直接展开:

  1. 先更新缓存,再更新数据库。
  2. 先更新数据库,再更新缓存。
  3. 先删除缓存,再更新数据库。
  4. 先更新数据库,再删除缓存。

四种策略无非就是先后顺序,一心把他总结为两个问题:

问题一:先更新数据库,还是先更新缓存?

问题二:先更新数据库,还是先删除缓存?

在这里,我们带着这两个问题来进行逐一展开。

一、先更新数据库,还是先更新缓存?

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

流程:【请求A】先将数据库的数据更新为 1,然后在更新缓存前,【请求 B】 将数据库的数据更新为 2,紧接着也把缓存更新为 2,然后 【请求A】更新缓存为 1

此时,数据库中的数据是 2,而缓存中的数据却是 1,出现了缓存和数据库中的数据不一致的现象。(此方案在实际中不建议采用)

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

流程:【请求A】先将缓存的数据更新为 1,然后在更新数据库前,【请求B】来了, 将缓存的数据更新为 2,紧接着把数据库更新为 2,然后 【请求A】将数据库的数据更新为 1

此时,数据库中的数据是 1,而缓存中的数据却是 2,出现了缓存和数据库中的数据不一致的现象。(此方案在实际中不建议采用)

🚀 小结

无论是【先更新数据库,再更新缓存】,还是【先更新缓存,再更新数据库】,这两个方案都存在并发问题,当两个请求并发更新同一条数据的时候,可能会出现缓存和数据库中的数据不一致的现象。

二、先更新数据库,还是先删除缓存?

🌵 旁路缓存策略

对于不更新缓存,而是删除缓存中的数据。然后读取数据时,发现缓存中没了数据之后,再从数据库中读取数据,更新到缓存中。我们将这种策略称为Cache Aside 策略,中文是叫旁路缓存策略。

旁路缓存策略又可以细分为「读策略」和「写策略」。

写策略的步骤:

  • 更新数据库中的数据;
  • 删除缓存中的数据。

读策略的步骤:

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

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

流程:现有一个数据初始值为20,【请求A】此时要将数据更新为21,先执行删除缓存操作,此时,另一个【请求B】要读取这个数据,查询缓存未命中后,会从数据库读取到该数据为20,并写入到缓存中,这时【请求A】继续更改数据库,将数据进行更新为21

此时,缓存中的数据是20(旧值),而数据库中却是21(新值),缓存和数据库的数据不一致。

所以先删除缓存,再更新数据库,在「读 + 写」并发的时候,还是会出现缓存和数据库的数据不一致的问题。(此方案在实际中不建议采用)

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

流程:假如某个数据在缓存中不存在,【请求 A】 读取数据时从数据库中查询到其为 20,在未写入缓存中时另一个【请求 B】 更新数据。它更新数据库中的数据为 21,并且清空缓存。这时【请求 A 】把从数据库中读到的 数据20写入到缓存中。

最终,该数据在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库数据不一致。

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

因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且删除了缓存,请求 A 才更新完缓存的情况。

而一旦【请求 A】 早于【请求 B】 删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现这种不一致的情况。

所以,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的

三、数据一致性解决方案

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

先更新缓存,再更新数据库 也同理。

方案:

  • 在更新缓存前先加个分布式锁,保证同一时间只运行一个请求更新缓存,就会不会产生并发问题了,当然引入了锁后,对于写入的性能就会带来影响。
  • 在更新完缓存时,给缓存加上较短的过期时间,这样即时出现缓存不一致的情况,缓存的数据也会很快过期,对业务还是能接受的。

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

方案:

伪代码:

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

「延迟双删」:

加个睡眠时间,主要是为了确保【请求 A】 在睡眠的时候,【请求 B】 能够在这这一段时间完成「从数据库读取数据,再把缺失的缓存写入缓存」的操作,然后【请求 A】 睡眠完,再删除缓存。

所以,【请求 A】 的睡眠时间就需要大于【请求 B】 「从数据库读取数据 + 写入缓存」的时间。但具体睡眠多久,很难评估出来,所以这个方案也只是尽可能保证一致性而已,极端情况下,依然也会出现缓存不一致的现象。

故以上的所有策略中,最建议使用的还是「先更新数据库,再删除缓存」。

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

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

相关文章

vue3性能提升表现具体在哪里(简洁版)

总的来说,vue3的效率提升的原因就是对一些静态的东西做了各种优化: 静态的节点会被缓存 连续多个的静态节点会被编译成字符串 静态的事件处理函数也会被缓存 dom树对比的时候只对比动态的节点,.因为vue3将每个dom上的动态节点都以数组的形式保存在block 节点中,对比只需要循…

匿名函数( lambda 表达式)

在 C 中,匿名函数也被称为 lambda 表达式。C11 引入了 lambda 表达式,使得在需要函数对象(函数符)的地方可以使用匿名函数来代替。 lambda 表达式的基本语法如下: [capture list] (parameter list) -> return typ…

uni-app项目由hbuilder项目转化为cli项目

1.背景 原uni-app项目是通过hbuilder创建的,运行以及打包都要依赖于hbuilder运行;一般在vscode开发,在hbuilder运行比较怪异;后续希望脱离hbuilder运行并能通过构建平台进行打包,因此将hbuilder项目转化为cli项目 2.…

JVM-CMS

when 堆大小要求为4-8G 原理 初始标记:执行CMS线程->STW,标记GC Root直接关联的对象->低延迟 并发标记:执行CMS线程和业务线程,从GC Root直接关联的对象开始遍历整个对象图 重新标记:执行CMS线程->STW&a…

Mybatis-Plus快速入门

目录 一、基础工程 1、创建一个数据库:mp 2、添加数据 3、创建初始工程 4、添加依赖 二、Mybatis Mybatis-Plus 1、创建子工程:mybatis-plus-simple 2、在子工程下添加配置 2.1Mybatis实现查询User 2.1.1、编写User实体对象 2.1.2、编写UserMa…

【业务功能篇93】微服务-springcloud-多线程-异步处理-异步编排-CompletableFutrue-实战运用

异步处理编排 我们可以在商品详细信息查询的位置实现CompletableFuture的异步编排处理。 根据业务分析:3.4.5数据接口的入参信息需要来源于1数据接口的返回信息,也就是skuid 所以可以设计 1 3 4 5 串行线程 ,而 3 4 5依赖1 ,需要等…

Redis项目实战——优惠券秒杀

目录 Redis自增功能解决全局唯一IDRedis实现优惠券秒杀的主要思路实现过程中出现的问题及解决方法超卖问题方案1 悲观锁方案2 乐观锁 一人一单问题分布式锁如何用Redis实现分布式锁? Redis优化秒杀消息队列实现异步秒杀List发布订阅模式Stream Redis自增功能解决全局…

MySql013——函数

一、数据处理函数 1.1、文本处理函数 函 数 说 明 Left() 返回串左边的字符Length() 返回串的长度Locate() 找出串的一个子串Lower() 将串转换为小写LTrim() 去掉串左边的空格Right() 返回串右边的字符RTrim() 去掉串右边的空格Soundex() 返回串…

React 钩子汇总

React 钩子 一、常用的 React 钩子: 1. useState 用于在函数式组件中添加状态管理。它返回一个状态值和一个更新状态的函数,让你可以在组件中追踪和更新状态。 2. useEffect 用于在组件渲染完成后执行副作用操作,比如数据获取、订阅等。…

Relation Extraction as Open-book Examination: Retrieval-enhanced Prompt Tuning

本文是LLM系列文章,针对《Relation Extraction as Open-book Examination: Retrieval 关系提取作为开卷测试:检索增强提示调整 摘要1 引言2 方法3 实验4 相关工作5 结论 摘要 经过预训练的语言模型通过表现出显著的小样本学习能力,对关系提取…

基于python的反爬虫技术的研究设计与实现

摘 要 当下的网络是复杂的,网络上的信息非常的丰富,但也造成了大量的信息堆积,特别是大量的重复信息被反复的推送给用户。这是一个流量的时代,很多社会群体都会聚焦具备流量潜力的信息,从而发生蹭热度等行为来提升自己…

Windows环境下的Tomcat服务器安装和配置教程,包括外网远程访问的设置方法

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器,不仅名字很有趣&#xff0…

Docker consul 容器服务自动发现和更新

目录 一、什么是服务注册与发现 二、Docker-consul集群 1.Docker-consul consul提供的一些关键特性 2.registrator 3.Consul-template 三、Docker-consul实现过程 以配置nginx负载均衡为例 先配置consul-agent ,有两种模式server和client 四、Docker-cons…

ChatGPT HTML JS Echarts实现热力图展示

热力图是一种常用的数据可视化图表,主要用于展示数据的分布和密度情况。它通过使用不同颜色的热点来表示数据在地理或二维空间上的分布情况,从而直观地显示出数据的密集程度和趋势。 热力图的功能和作用如下: 1. 数据分布展示:热力图可以将大量数据以热点的形式展示在地理…

K8s 持久化存储有几种方式?一文了解本地盘/CSI 外接存储/K8s 原生存储的优缺点

当今云原生环境中,Kubernetes(K8s)已成为既定的容器编排工具。随着 K8s 的普及,存储也成为 K8s 用户关注的一个重要问题:为了满足不同的场景需求,K8s 可以支持基于不同架构的多种存储方案。这些方案间有什么…

Spark与Flink的区别

分析&回答 (1)设计理念 1、Spark的技术理念是使用微批来模拟流的计算,基于Micro-batch,数据流以时间为单位被切分为一个个批次,通过分布式数据集RDD进行批量处理,是一种伪实时。 2、Flink是基于事件驱动的,是面向流的处理框架, Flink基于…

0基础学习VR全景平台篇 第95篇:VR实景智慧导航操作手册

一、实景导航前期准备工作及点位采集 (一)实景导航前期准备工作 (1)拍摄设备 1.推荐相机:全画幅的佳能 Canon EOS​ 5D Mark IV 2.搭配镜头:原厂的佳能 Canon EF卡口 8-15mm 全画幅鱼眼镜头 3.三角架 …

vue3中的useAttrs和props的区别

在vue3中&#xff0c; 提供了一个 useAttrs 的方法 它接收到的参数一 prop中可以接收到的数据是基本一样的 如果我们想自已写一个组件&#xff0c; 把 elementPlus 中的期中一个组件封装一下。 可以这样做 1.新建一个 自定义组件 myBtnCom <template><div class"…

Java:SpringBoot实现定时任务Scheduled

代码示例 package com.example.demo.config;import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled;import java.text.SimpleDate…

stencilJs学习之构建 Drawer 组件

前言 在之前的学习中&#xff0c;我们已经掌握了 stencilJs 中的一些核心概念和基础知识&#xff0c;如装饰器 Prop、State、Event、Listen、Method、Component 以及生命周期方法。这些知识是构建复杂组件和应用的基础&#xff0c;而抽屉组件是一个很好的示例&#xff0c;能够…