【实践篇】Redis缓存和数据库一致性问题

Redis缓存和数据库一致性问题


在这里插入图片描述

0. 前言

确保缓存和数据库之间的数据一致性是一项挑战,这需要考虑的因素包括更新策略、操作顺序以及是否引入消息队列等等。

  1. 更新缓存还是删缓存?
    一般来说,推荐的做法是删除缓存,而不是更新缓存。原因是更新缓存会引入更复杂的一致性问题。例如,如果在更新缓存之前,数据库的数据已经发生了变化,那么更新后的缓存数据将是过时的。另一方面,如果删除了缓存数据,那么下次请求时,如果缓存中没有数据,就会去数据库中查找,并将查找的结果存入缓存,这样可以确保缓存中的数据是最新的。

  2. 先更新数据库,再删除缓存,还是先删除缓存,再更新数据库?
    常见的推荐做法是先更新数据库,再删除缓存。原因是如果先删除缓存,那么在缓存删除后到数据库更新这段时间里,新的请求可能会将旧的数据重新加载到缓存中,导致数据不一致。

  3. 为什么要引入消息队列保证一致性?
    引入消息队列的目的是异步处理数据更新和缓存删除操作,减少这两个操作之间的时间差,从而降低数据不一致的风险。另外,消息队列还可以提供重试机制,以应对因为临时的故障导致的操作失败。

  4. 延迟双删会有什么问题?要不要用?
    延迟双删是指先删除缓存,再更新数据库,然后延迟一段时间后再次删除缓存。这种策略的目的是防止在更新数据库后新的请求将旧的数据加载到缓存中。但是,这种策略有一个问题,就是需要确定合适的延迟时间,如果延迟时间过短,可能无法阻止旧数据被加载到缓存中;如果延迟时间过长,可能会导致缓存的数据过期。因此,是否使用这种策略取决于具体的应用场景和需求。

题。

参考资料

  1. Redis官方文档:https://redis.io/
  2. Redis实战(书籍)
  3. Redis设计与实现(书籍)

1. 缓存和数据库的数据不一致是如何发生的?

首先,我们需要明确"数据的一致性"的具体含义。在这里,一致性包括两种情况:

当缓存中存在数据时,缓存的数值应与数据库中的值相同;
当缓存中没有数据时,数据库中的值必须是最新的。

不符合这两种情况的情况就属于缓存和数据库之间的数据不一致问题。

然而,缓存数据不一致的发生情况以及相应的应对方法会根据缓存的读写模式而异。根据是否接受写请求,我们可以将缓存分为读写缓存和只读缓存。

对于读写缓存而言,如果需要对数据进行增删改操作,就需要在缓存中进行,并根据所采取的写回策略决定是否同步写回数据库。同步直写策略要求在写入缓存时同时将数据同步写回数据库,以保持缓存和数据库中的数据一致。异步写回策略则表示在写入缓存时不立即同步写回数据库,而是等到数据从缓存中淘汰时再写回数据库。然而,如果在数据还未写回数据库时缓存发生故障,那么数据库将无法获取最新的数据。

因此,对于读写缓存来说,要保证缓存和数据库的数据一致性,需要采用同步直写策略。但需要注意的是,采用该策略时需要同时更新缓存和数据库。因此,在业务应用中需要使用事务机制,确保缓存和数据库的更新具有原子性,即要么同时更新,要么都不更新并返回错误信息以进行重试。否则,无法实现同步直写。

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

接下来,我们来讨论只读缓存。对于只读缓存,如果有数据新增,会直接写入数据库;而对于有数据修改或删除的情况,需要将只读缓存中的数据标记为无效。这样的处理方式会导致缓存缺失,当应用再次访问这些增删改的数据时,由于缓存中没有相应的数据,就会从数据库中读取数据并将其存入缓存。这样,后续再次访问数据时就可以直接从缓存中读取。

以java向MySQL中写入和修改数据为例,下图展示了数据的增删改操作具体过程:

java-> MySQL
  Insert          数据X直接在数据库中新增
  Update          数据X直接在数据库中修改
  Delete          数据X直接在数据库中删除

如果执行的是修改或删除操作,还会删除缓存中的数据X。

通过图中可以看出,无论是新增、修改还是删除数据X,java上运行的应用都会直接在数据库中进行相应的操作。当然,如果应用执行的是修改或删除操作,还会从缓存中删除数据X。这个过程可以确保缓存和数据库的数据一致性。

1. 删除数据的情况:

当应用执行删除操作时,需要在数据库中删除相应的数据,并且还需要在缓存中删除对应的数据。如果删除数据库操作成功,但在删除缓存时发生错误,就会导致数据不一致的问题。此时,数据库中的数据已经被删除,而缓存中的数据仍然存在,从而导致缓存和数据库的数据不一致。

举个例子来说明这种情况:假设应用要删除数据X的值,首先成功地从数据库中删除了X,但在删除缓存中的X时遇到了错误。这种情况下,数据库中的X已经被删除,但缓存中仍然存在X的数据。如果此时另一个并发请求访问X,根据正常的缓存访问流程,它会首先在缓存中查询X,但由于缓存中仍然存在,它会读取到已经被删除的数据X,导致数据不一致。

2. 修改数据的情况:

在执行数据修改操作时,应用需要先更新数据库中的数据,然后再删除缓存中的数据。如果这两个操作无法保证原子性,就可能出现数据不一致的问题。假设应用先更新数据库,然后再删除缓存,如果在删除缓存时遇到错误,就会导致数据库中的数据是新值,而缓存中的数据是旧值,从而导致数据不一致。

举个例子来说明这种情况:应用要将数据X的值从10更新为3,首先成功地更新了数据库中的值,然后在删除缓存中的X时遇到错误。这种情况下,数据库中X的新值为3,但缓存中X的值仍然是旧值10,导致数据不一致。如果此时有其他并发请求来访问X,按照正常的缓存访问流程,它会先在缓存中查询,但由于缓存中仍然存在,它会读取到旧值10,进一步导致数据不一致。

当我们考虑缓存和数据库的一致性问题时,除了操作失败的情况,还需要考虑并发问题。并发问题可能会导致缓存和数据库之间的数据不一致。让我们来看一下这两种情况:

  1. 并发写: 如果有两个并发的写操作,一个写操作先更新数据库,然后更新缓存,另一个写操作在第一个写操作更新数据库后和更新缓存前更新了数据库和缓存,那么最后缓存中的数据将是第一个写操作的数据,而数据库中的数据是第二个写操作的数据,这就导致了数据不一致。

  2. 写后读: 如果一个写操作先更新了数据库,然后在更新缓存之前,一个读操作读取了数据库并将数据加载到了缓存,那么即使写操作后来更新了缓存,缓存中的数据也是旧的数据,这也会导致数据不一致。

为了解决这些问题,我们可以采用以下策略:

  1. 使用分布式锁: 我们可以在更新数据库和缓存时使用分布式锁,确保每次只有一个写操作可以执行。这样可以避免并发写的问题,但是会降低系统的并发性能。

  2. 先删除缓存,再更新数据库: 这是一种常见的策略,它可以避免写后读的问题。具体来说,我们先删除缓存,然后更新数据库。这样,如果在更新数据库后有一个读操作,由于缓存中没有数据,读操作会从数据库中读取最新的数据并加载到缓存中。

  3. 使用消息队列: 我们可以将更新缓存的操作放入消息队列中,确保更新数据库和更新缓存的操作按照正确的顺序执行。这样可以避免并发写和写后读的问题,但是会增加系统的复杂性。

以上只是一些基本的策略,具体的实现可能会根据业务需求和系统环境的不同而有所不同。总的来说,维护缓存和数据库的一致性需要综合考虑多种因素,包括但不限于如何更新数据、操作的顺序、是否引入消息队列以及如何处理并发等问题。

猜你喜欢

转载自blog.csdn.net/wangshuai6707/article/details/132680055