一文讲解Redis中的数据一致性问题

news/2025/2/27 17:31:00/

一文讲解Redis中的数据一致性问题

在技术派实战项目中,我们采用的是先写 MySQL,再删除 Redis 的方式来保证缓存数据库的数据一致性。
技术派教程
我举例说明一下。
对于第一次查询,请求 B 查询到的缓存数据是 10,但 MySQL 被请求 A 更新为了 11,此时数据库缓存不一致。

但也只存在这一次不一致的情况,对于不是强一致性的业务,可以容忍。

当请求 B 第二次查询时,因为请求 A 更新完数据库缓存删除了,所以请求 B 这次不会命中缓存,会重新查一次 MySQL,然后回写到 Redis。
缓存数据库又一致了。

那再来说说为什么要删除缓存而不是更新缓存

因为相对而言,删除缓存的速度比更新缓存的速度要快得多。举个例子:假设商品 product_123 的当前库存是 10,现在有一次购买操作,库存减 1,我们需要更新 Redis 中的库存信息。

product_id = "product_123"
# 假设这是购买操作后的新库存值
new_stock = 9# 更新Redis中的库存信息
redis.set(product_id, new_stock)

更新操作至少涉及到两个步骤:计算新的库存值和更新 Redis 中的库存值。

假如是直接删除操作,直接就一步到位了:

product_id = "product_123"# 删除Redis中的库存缓存
redis.del(product_id)

三分恶面渣逆袭:删除<a class=缓存和更新缓存" />

假如是更新缓存,那么可能请求 A 更新完 MySQL 后在更新 Redis 中,请求 B 已经读取到 Redis 中的旧值返回了,又一次导致了缓存数据库不一致。

那再说说为什么要先更新数据库,再删除缓存

因为更新数据库的速度比删除缓存的速度要慢得多。因为更新 MySQL 是磁盘 IO 操作,而 Redis 是内存操作。内存操作比磁盘 IO 快得多(这是硬件层面的天然差距)。

那假如是先删除缓存,再更新数据库,就会造成这样的情况:

缓存中不存在,数据库又没有完成更新,此时有请求进来读取数据,并写入到缓存,那么在更新完缓存后,缓存中这个 key 就成了一个脏数据。

三分恶面渣逆袭:先更<a class=数据库还是先删缓存" />三分恶面渣逆袭:先更数据库还是先删缓存

目前最流行的缓存读写策略 Cache Aside Pattern(旁路缓存模式)就是采用的先写数据库,再删缓存的方式。

  • 失效:应用程序先从缓存读取数据,如果数据不存在,再从数据库中读取数据,成功后,放入缓存
  • 命中:应用程序从缓存读取数据,如果数据存在,直接返回。
  • 更新:先把数据写入数据库,成功后,再让缓存失效。

左耳朵耗子:Cache Aside Pattern

那假如对一致性要求很高,该怎么办呢?

缓存数据库数据不一致的原因,常见的有两种:

  • 缓存删除失败
  • 并发导致写入了脏数据

那通常有四种方案可以解决。
img

①、引入消息队列保证缓存被删除

使用消息队列(如 Kafka、RabbitMQ)保证数据库更新和缓存更新之间的最终一致性。当数据库更新完成后,将更新事件发送到消息队列。有专门的服务监听这些事件并负责更新或删除缓存

三分恶面渣逆袭:消息队列保证key被删除三分恶面渣逆袭:消息队列保证key被删除

这种方案很不错,缺点是对业务代码有一定的侵入,毕竟引入了消息队列嘛。

②、数据库订阅+消息队列保证缓存被删除

可以专门起一个服务(比如 Canal,阿里巴巴 MySQL binlog 增量订阅&消费组件)去监听 MySQL 的 binlog,获取需要操作的数据。

技术派教程

然后用一个公共的服务获取订阅程序传来的信息,进行缓存删除。

三分恶面渣逆袭:<a class=数据库订阅+消息队列保证key被删除" />三分恶面渣逆袭:数据库订阅+消息队列保证key被删除

这种方式虽然降低了对业务的侵入,但增加了整个系统的复杂度,适合基建完善的大厂。

③、延时双删防止脏数据

简单说,就是在第一次删除缓存之后,过一段时间之后,再次删除缓存

主要针对缓存不存在,但写入了脏数据的情况。在先删缓存,再写数据库的更新策略下发生的比较多。

三分恶面渣逆袭:延时双删

这种方式的延时时间需要仔细考量和测试。

④:设置缓存过期时间兜底

这是一个朴素但有用的兜底策略,给缓存设置一个合理的过期时间,即使发生了缓存数据库的数据不一致问题,也不会永远不一致下去,缓存过期后,自然就一致了。


http://www.ppmy.cn/news/1575291.html

相关文章

纷析云:赋能企业财务数字化转型的开源解决方案

在企业数字化转型的浪潮中&#xff0c;财务管理的高效与安全成为关键。纷析云凭借其开源、安全、灵活的财务软件解决方案&#xff0c;为企业提供了一条理想的转型路径。 一、开源的力量&#xff1a;自主、安全、高效 纷析云的核心优势在于其100%开源的财务软件源码。这意味着…

将DeepSeek接入vscode的N种方法

接入deepseek方法一:cline 步骤1:安装 Visual Studio Code 后,左侧导航栏上点击扩展。 步骤2:搜索 cline,找到插件后点击安装。 步骤3:在大模型下拉菜单中找到deep seek,然后下面的输入框输入你在deepseek申请的api key,就可以用了 让deepseek给我写了一首关于天气的…

前端兼容处理接口返回的文件流或json数据

参考文档&#xff1a;JavaScript | MDN 参考链接&#xff1a;Blob格式转json格式&#xff0c;拿到后端返回的json数据_blob转json-CSDN博客 参考链接&#xff1a;https://juejin.cn/post/7117939029567340557 场景&#xff1a;导入上传文件&#xff0c;导入成功&#xff0c;…

【NLP 23、预训练语言模型】

人类发明后悔&#xff0c;来证明拥有的珍贵 —— 25.1.15 Bert的优势&#xff1a;① 预训练思想 ② Transformer模型结构 一、传统方法 VS 预训练方式 Pre-train&#xff1a; ① 收集海量无标注文本数据 ② 进行模型预训练&#xff0c;并在任务模型中使用 Fine-tune&#xff1a…

vmware:新装ubuntu无法使用ssh连接或者复制粘连

前言 如标题所示&#xff0c;我在使用vmware-workstation安装ubuntu22.04LTS桌面版虚拟机后&#xff0c;发现没办法使用ssh远程连接&#xff0c;或者与宿主机之间的复制粘连功能。 解决方案 卡了一天&#xff0c;以为是网络通讯问题&#xff0c;没想到是内部服务的问题。 远程连…

uniapp-X 对象动态取值

有个对象&#xff0c;例如 const data{age:12,list:[1,2,3,4]} 有个函数如下 export function getValueByPath(obj:UTSJSONObject, path:string):any {const current obj.getAny(path) as any;// 返回最终的值return current; } 期待 通过执行getValueByPath("xx.xx…

C++ Primer 泛型算法定制操作

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

MySQL---存储过程详解

目录 一、介绍 二、基础语法 三、变量 四、流程控制 五、参数 六、游标 七、条件处理程序 八、存储函数 一、介绍 存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合&#xff0c;调用存储过程可以简化应用开发人员的很多工作&#xff0c;减少数据在数据库…