当前位置: 首页 > news >正文

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

一文讲解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 更新完 MySQL 后在更新 Redis 中,请求 B 已经读取到 Redis 中的旧值返回了,又一次导致了缓存和数据库不一致。

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

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

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

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

三分恶面渣逆袭:先更数据库还是先删缓存三分恶面渣逆袭:先更数据库还是先删缓存

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

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

左耳朵耗子:Cache Aside Pattern

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

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

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

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

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

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

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

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

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

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

技术派教程

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

三分恶面渣逆袭:数据库订阅+消息队列保证key被删除三分恶面渣逆袭:数据库订阅+消息队列保证key被删除

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

③、延时双删防止脏数据

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

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

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

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

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

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

相关文章:

  • Linux红帽:RHCSA认证知识讲解(二)配置网络与登录本地远程Linux主机
  • Qt在Linux嵌入式开发过程中复杂界面滑动时卡顿掉帧问题分析及解决方案
  • 策略模式介绍和代码示例
  • Megatron-LM:使用模型并行训练数十亿参数的语言模型
  • 【SpringBoot+Vue】博客项目开发一:基础框架搭建
  • Python游戏编程之赛车游戏6-3
  • java23种设计模式-桥接模式
  • 【关于seisimic unix中使用suedit指令无法保存问题】
  • 网络 - Socket编程
  • Go 协程池完整解析(原理+实践+性能分析
  • 第二章-续:辅助功能
  • EX_25/2/22
  • 第5章 软件工程(二)
  • Crack SmartGit
  • vue3中Watch和WatchEffect的用法和区别
  • 调用DeepSeek API 增强版纯前端实现方案,支持文件上传和内容解析功能
  • sam2 windows 编译安装
  • springboot单机支持1w并发,需要做哪些优化
  • 国产编辑器EverEdit - 如何在EverEdit中创建工程?
  • 使用Uni-app实现语音视频聊天(Android、iOS)
  • 购买主机可以做网站吗/长春网站建设解决方案
  • 澳门/重庆seo优
  • 2022年新闻热点事件/关键词优化推广公司排名
  • 昆明网站建设服务/百度手机助手官网下载
  • 郑州网站建设 智巢/seo关键词选择及优化
  • 网站营运费/怎么创建网站教程