写点什么

解决缓存与数据库数据不一致的问题,这篇文章告诉你如何做!

  • 2023-05-27
    湖南
  • 本文字数:3580 字

    阅读完需:约 12 分钟

缓存是提高应用程序性能和响应速度的关键组件之一。缓存可以帮助减少数据库查询次数,从而减轻服务器负担并加快页面加载速度。然而,缓存与数据库一致性是分布式系统中常见的问题,因为缓存和数据库之间可能存在数据不一致的情况。为了解决这个问题,我们需要采用适当的策略来保证缓存和数据库中的数据始终保持一致。本文将探讨缓存与数据库一致性问题的处理策略及方案。

一、概述

1.1、什么是缓存与数据库一致性?

缓存与数据库不一致的情况指的是,当某个值被缓存起来时,在数据库中发生了更改,但是缓存中的值没有被更新,导致缓存中的数据与数据库中的数据不同步的问题。


这种不一致性可能会导致存在脏数据,也就是说,在缓存中存在着已经被删除或者已经过期的数据。这样会导致应用程序返回不正确的结果,甚至可能导致安全漏洞和数据泄漏。

1.2、处理策略

为了保持缓存与数据库的一致性,应该考虑以下策略:

  1. 数据库先行在进行任何操作之前,需要检查数据库中是否存在要获取或修改的数据。如果数据存在,则直接使用数据库中的数据,并在需要更新或删除它时同时更新缓存。这种策略通常被称为“先验证数据库”。

  2. 缓存先行另一种策略是“先验证缓存”。在这种情况下,应用程序首先检查缓存是否已经保存有所需的数据。如果缓存中存在,则直接返回该数据。否则,从数据库中获取数据,并将其保存到缓存中。

  3. 双写策略双写策略是指每次数据更改都会同步更新数据库和缓存。当应用程序对数据库进行更改时,它还会更新缓存以保持同步。这种方法可以确保缓存和数据库始终保持同步,但可能会影响性能。

二、缓存一致性策略详解

为了解决缓存与数据库一致性的问题,针对不同的业务需求和场景,常见的几种策略如下:

1、Cache-Aside 模式(先更 DB 再删缓存)

这种策略是在每次读取数据时,缓存首先尝试从缓存中获取数据,如果不存在则从数据库中加载并添加到缓存中。这种方式可以保证缓存和数据库中的数据一致性,并且不会降低写入性能,但会影响读取性能,因为第一次读取需要从数据库中加载数据。这种策略适用于读频率较高,写频率较低的应用。当需要更新缓存数据时,先更新数据库,再删除相应的缓存,待下次请求时再重新加载并缓存数据。


优点:

  • 可以通过缓存来提高系统性能;

  • 缓存不是主要数据源,因此可以降低缓存的复杂度;

  • 数据库与缓存之间的一致性得到保证。


缺点:

  • 每次查询都需要访问缓存和数据库,会增加网络延迟和系统开销;

  • 缓存不是永久性的存储介质,需要定期刷新和维护。

2、Write-Through 模式(同时更新 DB 与缓存)

Write-Through 模式将缓存视为独立存储介质,每当执行写操作时,缓存与数据库会同时更新,以保证数据的一致性。


优点:

  • 写入操作可以在缓存和数据库之间同步进行,避免了不一致性问题;

  • 读取操作时,缓存可以有效地提高系统性能。


缺点:

  • 每次写入都需要访问缓存和数据库,会增加网络延迟和系统开销;

  • 由于缓存是永久性的存储介质,如果发生故障或失效,可能会导致数据丢失或不一致。

3、Write-Back 模式(先更缓存再异步更 DB)

Write-Back 模式将缓存视为主要数据源,在写操作时,只更新缓存而不是数据库,当缓存中的数据发生变化时,再异步地将最新的数据写回到数据库中。


优点:

  • 写操作可以在缓存中进行,不需要访问数据库,避免了网络延迟和系统开销;

  • 读取操作时,缓存可以有效地提高系统性能。


缺点:

  • 缓存是永久性的存储介质,如果发生故障或失效,可能会导致数据丢失或不一致;

  • 异步写入可能会导致缓存与数据库之间的数据不一致。

4、Read-Through 模式(先读缓存没有再读 DB)

将缓存视为主要数据源,在读取操作时,只从缓存中获取数据而不是从数据库中获取。当数据不存在于缓存中时,会自动从数据库中读取并将其缓存,以保持数据的一致性。


在使用 Read-Through 模式时,以下是其常见的几个步骤:

  1. 应用程序首先检查缓存是否存在所需的数据;

  2. 如果缓存中不存在数据,则应用程序向缓存发起请求,并指定缓存需要加载哪些数据;

  3. 缓存接收到请求后,从数据库中加载数据,并将其写入缓存;

  4. 缓存返回所需的数据给应用程序。

5、Write-Around 模式(更新 DB 不更新缓存)

Write-Around 是一种常见的缓存策略之一,它是写入时跳过(或绕过)缓存直接写入持久化存储的一种方式。当应用程序需要更新数据时,在使用 Write-Around 策略时,数据将直接被写入持久化存储,而不会被写入缓存。


由于数据没有被写入缓存,因此缓存中不会存在最新更新的数据。这种缓存策略通常适用于只有一部分数据需要被缓存的场景,例如只需要缓存部分热点数据,对于其他数据则采用 Write-Around 方式直接写入持久化存储。这样可以避免缓存空间被占满,同时也能减少因为数据失效而带来的性能损耗。但是,Write-Around 策略下,缓存中的数据可能不能及时更新,从而导致数据不一致的问题。


优点:

  1. 节省缓存空间:Write-Around 可以避免将所有的数据都放入缓存中,从而节省缓存空间,减轻缓存压力。

  2. 避免缓存污染:Write-Around 可以避免因为缓存污染而导致性能问题和数据不一致的情况。

  3. 适用于只需要缓存部分数据的场景:Write-Around 通常适用于只需要缓存部分热点数据的场景,对于其他数据则直接写入持久化存储。


缺点:

  1. 数据可能不能及时更新:由于 Write-Around 策略下缓存中不会存在最新更新的数据,因此可能会导致数据不一致的问题。

  2. 需要额外的 I/O 操作:使用 Write-Around 策略需要额外的 I/O 操作来写入持久化存储,可能会带来一定的性能损耗。

三、相关问题

1)什么是缓存与数据库一致性问题?

当应用程序中使用了缓存来提高读取速度时,如果缓存中的数据与数据库中的数据不一致,将会发生缓存与数据库不一致的问题。这个问题可能会导致应用程序的错误行为和数据不一致。


2)你如何解决缓存与数据库一致性问题?

解决缓存与数据库一致性问题的方法包括:

  • 强制刷新缓存:强制缓存重新从数据库中获取最新数据。

  • 实现缓存自动更新:当数据库中的数据发生变化时,自动通知缓存进行更新。

  • 使用 write-through 或 write-behind 策略:在写入缓存时,同时写入数据库;或者先写入缓存,再异步地写入数据库,以保证缓存与数据库的数据一致性。

  • 设置缓存失效时间:缓存中的数据有一个过期时间,当超过这个时间时,缓存会自动失效并重新从数据库中获取最新数据。


3)write-through 和 write-behind 策略有什么区别?

write-through 和 write-behind 都是保持缓存和数据库一致性的策略,区别在于:

  • write-through:在写入缓存时,同时写入数据库。这种策略可以确保缓存和数据库中的数据是一致的,但是可能会影响性能,因为每次写入都需要等待数据库的返回。

  • write-behind:先写入缓存,再异步地写入数据库。这种策略可以提高性能,因为写操作不需要等待数据库的返回。但是可能会出现缓存和数据库不一致的情况,因为如果在缓存更新之后、异步写入数据库之前,数据库发生了变化,那么缓存和数据库就不一致了。


4)什么时候使用缓存?

当需要快速读取数据时,可以使用缓存。常见的应用场景包括:

  • 频繁读取的数据:比如网站的首页、商品列表等。

  • 计算代价高昂的数据:比如经过复杂计算得出的结果,可以将计算结果缓存起来,避免重复计算。

  • 数据库访问频繁的应用:比如电商网站、社交网络等,缓存可以减轻数据库的压力,提高应用程序的性能。


5)如何判断缓存是否命中?

当应用程序需要获取数据时,首先从缓存中查询。如果数据存在于缓存中,则缓存命中;否则,缓存未命中,需要从数据库中获取数据。

四、小结

4.1、缓存策略一般组合使用

这些缓存策略可以根据实际情况进行组合使用,以达到最优的效果。例如,可以使用 Write-Through 和 Read-Through 相结合的方式来避免数据不一致的问题;也可以使用 Write-Back 和 Refresh-Ahead 相结合的方式来提高系统性能和响应速度。需要注意的是,不同的缓存策略对于不同的应用场景和需求可能会产生不同的影响,因此需要根据具体情况选择合适的缓存策略和组合方式。

4.2、终极方案是什么?

选择方案时,面临的几个问题


1)缓存选择删除还是更新

一般来说考虑先删除再新增,在缓存中一般是线程 A 更新 DB 后删除缓存就结束,等下一个线程读取时再去创建缓存。相比线程 A 更新 DB 再更新缓存后者风险会小一点,当然在大量数据变更时还会更明显。


2)先删除缓存还是先更新 DB


  • 先删缓存再更新 DB

当线程 A 已经删除了缓存,但还没有更新 DB 时。线程 B 读就会创建缓存这时缓存就还是旧值。后续所有读取的线程都是读到缓存中的旧值。


  • 先更新 DB 再删缓存(推荐)

当线程 A 已经更新了 DB,但还没有删除缓存。线程 B 读就会读到缓存中的旧值。但当线程 A 删除了缓存,后续所有的线程就会重新用 DB 中的新值创建新的缓存,所以后续所有线程读的都是新值。


小结:固从大的方向上来考虑选择先更新 DB 再删除缓存比较好。


3)延时双删

延时双删综合了以上 2 种方案的优点,所以简单一致性可以考虑使用先更新 DB 再删除缓存的方案,再高级点就是延时双删的方案。


3)终极方案就是加锁


作者:玄明 Hanko

链接:https://juejin.cn/post/7237123113325051959

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
解决缓存与数据库数据不一致的问题,这篇文章告诉你如何做!_Java_做梦都在改BUG_InfoQ写作社区