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

news2025/1/12 18:14:05

文章目录

  • 1、错误的解决方案
    • 1.1、 先更新数据库,再删除缓存
    • 1.2、 先更新数据库,再更新缓存
    • 1.3、 先删除缓存,再更新数据库
    • 1.4、 先更新缓存,再更新数据库
  • 2、正确的解决方案
    • 2.1、使用 CAS
    • 2.2、使用分布式锁
    • 2.3、使用消息队列异步更新
    • 2.3、将数据库更新和缓存更新放在同一个事务中

1、错误的解决方案

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

若数据库更新成功,删除缓存操作失败,则此后读到的都是缓存中过期的数据,造成不一致问题。

1.2、 先更新数据库,再更新缓存

同删除缓存策略一样,若数据库更新成功缓存更新失败则会造成数据不一致问题。

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

在这里插入图片描述

1.4、 先更新缓存,再更新数据库

若缓存更新成功数据库更新失败, 则此后读到的都是未持久化的数据。因为缓存中的数据是易失的,这种状态非常危险。
在这里插入图片描述

2、正确的解决方案

2.1、使用 CAS

CAS (Check-And-Set 或 Compare-And-Swap)是一种常见的保证并发安全的手段。CAS 当且仅当客户端最后一次取值后该 key 没有被其他客户端修改的情况下,才允许当前客户端将新值写入。

func CAS(oldVal, newVal) {
    if cache.get() == oldVal {
        cache.set(newVal)
    }
}

在这里插入图片描述

  • 目前一些兼容 Redis 协议的中间件已经提供了 CAS 命令的支持,比如阿里的 Tair 以及腾讯的 Tendis。
  • Redis 官方本身是不支持CAS的操作,但是我们可以通过WATCHMULTI 命令实现类似的效果
  • WATCH 命令用于监视一个或多个键的变化,并在某个键被修改后取消事务,从而确保事务的原子性
  • MULTI 命令用于开始一个事务,将多个命令打包成一个事务,然后一次性执行。如果在执行事务期间有其他客户端对事务中的键进行修改,那么事务会被取消

2.2、使用分布式锁

CAS 假设发生并发问题的概率不大, 所以 CAS 也被称为乐观锁。那么悲观锁能否解决我们的问题呢?

还是以「先更新数据库,再更新缓存」方案中两个写线程竞争为例, 我们要求任何线程在写入或读取数据库前都需要获取排它锁。
在这里插入图片描述
分布式锁同样可以解决并发问题,只是成本可能略高。

2.3、使用消息队列异步更新

使用消息队列实现异步更新时,可以将缓存更新的请求发送到消息队列中,由消息队列异步地处理缓存更新操作。下面是一个简单的案例:

假设有一个电商网站,需要对商品信息进行缓存。当用户访问商品详情页面时,先从缓存中读取商品信息,如果缓存中没有,则从数据库中读取。

  • 当商品信息发生变化时,需要更新缓存中的数据。这时可以通过消息队列异步更新缓存,具体步骤如下:

  • 当商品信息发生变化时,先更新数据库中的数据。

  • 将商品信息更新请求发送到消息队列中。

  • 消息队列异步地处理缓存更新操作,读取最新的商品信息,并将其更新到缓存中。

这样就可以保证缓存中的数据是最新的,避免了因为缓存中的数据过期而导致的数据不一致问题。同时,使用消息队列可以提高更新的可靠性和性能,避免因为缓存更新失败而导致的数据库和缓存数据不一致问题。


为什么异步更新可以解决

  • 异步更新缓存:当商品信息发生变化时,先更新数据库中的数据,然后将缓存更新请求发送到消息队列中,由消息队列异步地处理缓存更新操作。这样,即使缓存更新失败,也不会影响数据库中的数据,仅仅是缓存中的数据不是最新的而已。

  • 消息队列的可靠性:消息队列通常具有高可靠性和高可用性,可以保证消息的可靠传输和处理。即使在消息队列出现故障的情况下,也可以通过消息队列的备份、重试等机制来保证消息的可靠性。因此,即使缓存更新失败,也可以通过重试等机制来保证缓存最终被更新。


如果通过异步更新,更新缓存还是失败了怎么办

  • 重试更新缓存:当缓存更新失败时,可以尝试重新更新缓存。可以设置重试次数和重试间隔时间,避免因为频繁重试而影响性能。

  • 回滚数据库更新:当缓存更新失败时,可以回滚数据库中的更新操作,保证数据库和缓存中的数据一致。但是,回滚操作可能会影响数据库中的其他操作,需要考虑到这个问题。

  • 延迟更新缓存:当缓存更新失败时,可以将缓存更新请求放入一个延迟队列中,一段时间后再次尝试更新缓存。这样可以避免频繁重试而影响性能,同时保证缓存最终被更新。

  • 使用读写分离:将读请求和写请求分别处理,读请求从缓存中读取数据,写请求先更新数据库,再更新缓存。这样可以避免因为缓存更新失败而导致的数据不一致问题。

2.3、将数据库更新和缓存更新放在同一个事务中

可以保证在事务执行成功时,数据库和缓存中的数据都被更新;在事务执行失败时,数据库和缓存中的数据都不会被更新,保证了数据的一致性。

  • 要将MySQL和Redis放入同一个事务中,需要使用分布式事务处理框架,如XA或TCC。这些框架可以确保在整个事务过程中,MySQL和Redis的操作都能够得到正确的协调和同步。

  • XA:XA是一种分布式事务处理标准,它可以确保在多个数据库之间进行事务处理时,所有的操作都能够得到正确的协调和同步。在MySQL和Redis中都有XA实现,可以通过XA接口实现分布式事务。

  • TCC:TCC是一种补偿性事务处理框架,它通过预留资源、确认资源和释放资源三个步骤来实现分布式事务。在MySQL和Redis中都有TCC实现,可以通过TCC接口实现分布式事务。

  • 需要注意的是,使用分布式事务框架会增加系统的复杂性和开销,需要仔细考虑是否真正需要在MySQL和Redis之间实现分布式事务如果可以接受稍微降低一些数据一致性的风险,可以使用其他技术来实现MySQL和Redis之间的数据同步,如消息队列、定时任务等。

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

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

相关文章

字符串匹配—KMP算法

字符串匹配的应用非常广泛,例如在搜索引擎中,我们通过键入一些关键字就可以得到相关的搜索结果,搜索引擎在这个过程中就使用字符串匹配算法,它通过在资源中匹配关键字,最后给出符合条件的搜索结果。并且我们在使用计算…

SpringBoot解决用户重复提交订单(方式三:通过Redis实现-升级版)

文章目录前言1、方案实践1.1、引入Redis依赖1.2、添加Redis环境配置1.3、编写服务验证逻辑,通过 aop 代理方式实现1.4、在相关的业务接口上,增加SubmitLimit注解即可2、小结前言 在上一篇文章中,我们详细的介绍了随着下单流量逐渐上升&#…

【PyTorch】第二节:梯度的求解

作者🕵️‍♂️:让机器理解语言か 专栏🎇:PyTorch 描述🎨:PyTorch 是一个基于 Torch 的 Python 开源机器学习库。 寄语💓:🐾没有白走的路,每一步都算数&#…

python提取多个pdf特定页,并合并为新pdf文件

文章目录1,代码结构2,代码详解2.1,将范围字符串转成list2.2,获取pdf文件特定页2.3,将pdf页list合并为pdf文件并保存2.4,遍历所有要合并的文件,进行合并2.5,给出要合并的pdf文件及范围…

大模型学习

大模型学习计算机视觉方向ViTImage Token EmbeddingMulti-head Self-attentionStable Diffusionstable diffusion支持功能stable diffusion整体结构ClipText如何训练图像信息创建器(Image information creator)自动编码解码器(降噪绘制图形&a…

One Note插件——gem for onenote的安装

文章目录一、前言二、报错原因三、解决方法一、前言 平时写笔记都是用的OneNote来记录,但是Onenote没有 Markdown编辑器 ,写起来很不方便,搜索了解后知道gem for OneNote这个插件,于是下载安装了,但是插件每次都要手动勾选&#…

什么是小程序SDK?安全吗?

前面分享了很多小程序相关的内容,常常提到小程序SDK的概念,但似乎有很多小伙伴不是很理解,今天就来跟大家聊聊小程序SDK。 什么是小程序SDK? 小程序SDK是一种开发工具包,用于开发和构建小程序应用程序。它提供了一系列…

【thingsboard+chirpstack 下行数据通信测试】

这里写目录标题 7. 节点未收到 tb 平台下发数据原因分析7.1 收到的size为07.2 节点收不到数据7.3 可以收到数据的一组例子7.4 节点没收到数据原因分析本文主要描述 tb 下发的数据,节点接收不到原因分析。 主要是数据格式以及解析脚本的对应关系 7. 节点未收到 tb 平台下发数据…

Golang数据类型比较

直接使用比较的情况 分类说明是否能比较说明基本类型整型( int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune等)浮点数( float32/float64)复数类型( complex64/complex128)字符串&a…

《Vue3实战》 第一章 nods/npm安装、配置

1、nods.js安装(Windows) 1.1、下载并安装node https://nodejs.org/en/ , 安装到d盘nodejs目录 1.2、配置环境变量 path配置 1.3、配置全局包存放目录和缓存目录 在根目录下创建node_global(全局包存放目录)和node_cache&…

关于药物|新药|药品市场调研报告(实操资料分享)

药品市场调研报告是指对药品行业进行详细的市场情况研究和分析。往往伴随着药品市场调研目的地不同,如战略探索、新药开发、投资决策等,报告编辑的内容要点要求也不一样。但总的核心要点内容笔者已提炼,如下: 一、药品市场调研报告…

DeePMD-kit 配置环境备忘

版本 Conda Conda是一个开源的包管理系统和环境管理系统,用于安装多个版本的软件包及其依赖项,并在它们之间轻松切换。它可以在Linux、OS X和Windows上运行,是为Python程序创建的,但可以打包和分发任何软件。 conda enactivatec…

为何ChatGPT如此擅长编造故事?

“幻觉”——人工智能中的一个偏见性术语 AI聊天机器人(如OpenAI的ChatGPT)依赖于一种称为“大型语言模型”(LLM)的人工智能来生成它们的响应。LLM是一种计算机程序,经过数百万文本源的训练,可以阅读并生成“自然语言”文本语言,就像人类自然…

TCP报头结构和TCP协议特性

TCP报头结构 原端口号/目的端口号:表示数据是从哪个进程来,到哪个进程去; 32位序号/32位确认号:这个序号是取的发送方发送所用数据下一个字节的序号,发送方的序列号和接收方的确认号一样,才算接收成功&…

敏捷开发模式下如何用 PingCode 这类工具进行版本发布管理

在软件团队工作中,版本发布要达到好的发布效果,需要在版本发布前做好版本发布的规划,并对发布流程和进度进行管理 准备工作: 您已经创建了一个 PingCode 帐户【快速注册入口】 您创建了一个 PingCode Scrum或 Kanban 项目 您的…

【周末闲谈】文心一言,模仿还是超越?

个人主页:【😊个人主页】 系列专栏:【❤️周末闲谈】 周末闲谈 ✨第一周 二进制VS三进制 文章目录周末闲谈前言一、背景环境二、文心一言?(_)?三、文心一言的优势?😗😗😗四、文心一…

使用 arm 架构实例搭建 Harbor

使用 arm 架构实例搭建 Harbor事情准备(使用甲骨文云上实例时的准备事项)第1步,准备自签名证书第2步,安装Docker-ce第3步,构建arm镜像第4步,安装Harbor第5步,访问Harbor第6步,上传镜…

TensorFlow 深度学习第二版:1~5

原文:Deep Learning with TensorFlow Second Edition 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 深度学习 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 不要担心自己的形象,只…

2023年【第十四届蓝桥杯】省赛java b组填空题

第一题 令 S 1! 2! 3! ... 202320232023!,求 S 的末尾 9 位数字。 提示:答案首位不为 0。 考试时的想法以及题解: 如果我们直接按照题目描述直接来求每个阶乘和的话恐怕没有什么数据类型能够胜任,在考试时我一开始使用了…

Linux中的read/write和recv/send的区别,并使用recv/send实现简单的聊天功能

Linux中的read/write和recv/send的区别read/writeread/writeread/write的用法recv/sendrecv/sendrecv/send的用法LinuxLinuxLinux中的read/writeread/writeread/write和recv/sendrecv/sendrecv/send的区别下面是一个使用read/write进行文件读写操作的例子:下面是一个…