深入解析缓存与数据库数据不一致问题

news/2024/10/21 19:13:46/

缓存层是提高系统响应速度和扩展性的关键组件。然而,缓存层的引入也带来了数据一致性的挑战。

数据库中的数据发生变化时,如何确保这些变化能够及时且准确地反映到缓存中,是确保用户体验和系统可靠性的重要问题。

1. 数据一致性

首先,我们需要清楚什么情况符合【数据一致性】

不属于这两种情况的,则就是缓存数据库数据不一致问题了。

根据是否接收写请求,我们可以把缓存分成读写缓存只读缓存

1.1 读写缓存

对于读写缓存来说,如果要对数据进行增删改,就需要在缓存中进行,同时还要根据采取的写回策略,决定是否同步写回到数据库中。

所以,对于读写缓存来说,要想保证缓存数据库中的数据一致,就要采用同步直写策略

不过,需要注意的是,如果采用这种策略,就需要同时更新缓存数据库

所以,我们要在业务应用中使用事务机制,来保证缓存数据库的更新具有原子性,也就是说,两者要不一起更新,要不都不更新,返回错误信息,进行重试。否则,我们就无法实现同步直写。

当然,在有些场景下,我们对数据一致性的要求可能不是那么高,比如说缓存的是电商商品的非关键属性或者短视频的创建或修改时间等,那么,我们可以使用异步写回策略。

1.2 只读缓存

对于只读缓存来说,当有数据新增时,会直接写入数据库

当有数据删改时,需要把缓存中的数据标记为无效,当应用后续再访问这些增删改的数据时,因为发生缓存缺失,就会读取数据库,把数据放入缓存中了。

在这里插入图片描述
那么在上面的步骤中,“数据不一致”的情况会不会发生呢?

1.2.1 新增操作

如果是新增数据,则直接保存到数据库中,缓存中是没有值的。符合一致性的第2种情况,此时缓存数据库中数据是一致的。

1.2.2 删改数据

如果发生删改操作,既要更新数据库,也要删除缓存,这两个操作如果无法保证原子性,就会发生数据不一致的情况。

那么到底是先更新数据库,再删除缓存?还是先删除缓存,再更新数据库呢?我们分别来讨论下。

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

假如删除缓存成功,更新数据库失败,那么当应用再次访问数据时,发生缓存缺失,就去访问数据库,而数据库中的值为旧值。

在这里插入图片描述

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

如果更新数据库成功,删除缓存失败了,那数据库中的值是最新值,缓存中的值是旧值,这肯定是不一致的。
在这里插入图片描述

重试机制

从上面的流程可以看出,无论是哪种情况,都会发生数据不一致的情况,那如何解决呢?就是重试机制。

比如说,当应用删除缓存失败或更新数据库失败时,可以把要删除的缓存值或要更新的数据库值保存到消息队列中,然后从消息队列中重新读取这些值,再次的进行删除或更新。

在这里插入图片描述

并发情况

在上面的讨论中,说的是在更新数据库和删除缓存中,有一个失败的情况下,导致的“数据不一致”。

实际上,即使这两个操作都成功,当有大量并发请求时,应用还是有可能读到不一致的数据。

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

比如,线程 A 删除缓存成功,再还没来得及更新数据库时,线程 B 开始读取数据,它发现缓存缺失,然后去数据库读取数据,此时有两个问题:

  • 线程 B 读取到了旧值;
  • 线程 B 会把读取到的旧值,写入到缓存中,这会导致其他线程从缓存中读到旧值(会一直脏下去,直到缓存过期)

等到线程 B 执行完成后,线程 A 才开始更新数据库,此时数据库数据是最新值,缓存中是旧值。

在这里插入图片描述
针对这种情况,我们可以在线程 A 更新数据库后,sleep 一小段时间,再执行一次删除缓存操作。

线程 A sleep 的时间,就需要大于线程 B 读取数据再写入缓存的时间。

这个时间怎么确定呢?可以在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,以此为基础来进行估算。

当其它线程读取数据时,会发现缓存缺失,所以会从数据库中读取最新值。

在这里插入图片描述

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

当线程 A 更新数据库后,还没来得及删除缓存,此时线程 B开始读取数据,发生缓存命中,读取到旧数据。

但是,线程 A很快就执行删除缓存,让缓存失效了,后续的查询请求会发生缓存缺失,然后去查询数据库最新值了。

所以,这种情况对业务的影响较小。

但是,有一种极端情况
在这里插入图片描述
但是这种情况,理论上会出现,实际上概率特别低。

它需要满足,在读缓存缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

总结

下面我们来总结下,发生数据不一致的情况有哪些

  • 删除缓存或更新数据库某一步失败而导致的数据不一致,可以使用重试机制确保删除或更新操作成功。
  • 在删除缓存值、更新数据库的这两步操作中,有其他线程的并发读操作,导致其他线程读取到旧值,应对方案是延迟双删。

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

相关文章

go压缩的使用

基础:使用go创建一个zip func base(path string) {// 创建 zip 文件zipFile, err : os.Create("test.zip")if err ! nil {panic(err)}defer zipFile.Close()// 创建一个新的 *Writer 对象zipWriter : zip.NewWriter(zipFile)defer zipWriter.Close()// 创…

原理代码解读:基于DiT结构视频生成模型的ControlNet

Diffusion Models视频生成-博客汇总 前言:相比于基于UNet结构的视频生成模型,DiT结构的模型最大的劣势在于生态不够完善,配套的ControlNet、IP-Adapter等开源权重不多,导致难以落地。最近DiT-based 5B的ControlNet开源了,相比于传统的ControlNet有不少改进点,这篇博客将从…

RabbitMQ 作为消息中间件,实现了支付消息的异步发送和接收, 同步和异步相比 响应速度具体比较

在支付场景中,使用 RabbitMQ 实现消息的异步发送和接收与同步处理相比,响应速度和整体系统性能会有显著的不同。以下是同步和异步方式在响应速度上的详细比较: 1. 同步处理方式 在同步模式下,支付消息的处理流程通常是&#xf…

如何将 ECharts 图表插入 HTML Canvas

在 Web 开发中,数据可视化是一个常见且重要的需求。ECharts 是一个强大的图表库,而 HTML5 Canvas 则提供了灵活的绘图能力。今天,我们将探讨如何将这两者结合起来,实现将 ECharts 生成的图表插入到 HTML Canvas 中的特定位置。 为…

docker 文件目录迁移

文章参考 du -hs /var/lib/docker/ 命令查看磁盘使用情况。 du -hs /var/lib/docker/docker system df命令,类似于Linux上的df命令,用于查看Docker的磁盘使用情况: rootnn0:~$ docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 7 2 122.2…

【微信小程序_11_全局配置】

摘要:本文介绍了微信小程序全局配置文件 app.json 中的常用配置项,重点阐述了 window 节点的各项配置,包括导航栏标题文字、背景色、标题颜色,窗口背景色、下拉刷新样式以及上拉触底距离等。通过这些配置可实现小程序窗口外观的个性化设置,提升用户体验。 微信小程序_11_全…

智能听诊器:宠物健康管理的得力助手

随着科技的进步,智能听诊器已经成为宠物健康管理领域的一项革命性发明。它不仅能够实时监测宠物的心跳、呼吸频率和节律等关键生理指标,而且通过高精度的传感器捕捉到宠物心跳的微小变化和呼吸频率的微妙差异,为宠物主人提供了实时的健康数据…

oracle numtodsinterval

Oracle的numtodsinterval函数用于将数字转换为间隔值(INTERVAL)。这个函数接受一个数字和一个间隔种类作为参数,并返回一个间隔值。 种类参数可以是: DAY HOUR MINUTE SECOND 下面是一些使用numtodsinterval函数的例子&…