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

一文讲解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被删除

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

③、延时双删防止脏数据

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

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

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

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

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

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


http://www.kler.cn/a/562609.html

相关文章:

  • 爬虫开源项目
  • 探索浮点数在内存中的存储(附带快速计算补码转十进制)
  • 电子科技大学考研复习经验分享
  • 1.1部署es:9200
  • 第九节: Vue 3 中的 provide 与 inject:优雅的跨组件通信
  • SpringSecurity核心过滤器-SecurityContextPersistenceFilter
  • uniapp写的h5跳转小程序
  • LabVIEW 中 codeGenEngine.llb 工具库
  • 【c语言】字符函数和字符串函数(1)
  • 【SpringBoot】——分组校验、自定义注解、登入验证(集成redis)、属性配置方式、多环境开发系统学习知识
  • llaMa模型的创新
  • Mobaxterm服务器常用命令(持续更新)
  • 6.3 - UART串口数据发送之中断
  • Snapshot Compressed Imaging:打破传统成像的新视界
  • 接口测试-计算机网络基础扫盲
  • Linux | man 手册使用详解
  • 关于远程连接工具不能用hostname而只能用ip连接上的问题
  • flowable-ui 的会签功能实现
  • Hutool - Http:基于 HttpUrlConnection 的 Http 客户端封装
  • Vscode编辑器获取更新远程最新分支