关于redis与数据库的数据一致性,业界使用最多的是数据同步问题(双删策略)
🎉 双删策略
传说中的双删策略,听起来有点高大尚的样子,其实就是一种解决缓存和数据库数据不一致的问题的策略。接下来,让我们用生动有趣的例子和详细的解释来描述一下这种双删策略。
首先,我们来了解一下数据库和缓存的关系。在很多业务场景中,数据库是存储数据的唯一来源,而缓存则是为了提高读取数据的效率而存在的。因为数据库的读取速度相比缓存要慢很多,而且随着并发访问量的增加,数据库也会出现瓶颈,进而影响系统性能。那么,为了解决这个问题,我们可以先将数据存储到缓存中,当需要读取数据时,我们先从缓存中读取,如果没有再从数据库中读取。
但是,这种方案会带来一个问题:当数据库中的数据发生变化时,缓存中的数据却没有及时更新,导致缓存中的数据和数据库中的数据不一致。也就是说,当我们从缓存中读取数据时,可能得到的是旧的数据,而不是最新的数据。这种情况下,我们就需要考虑采用双删策略。
双删策略的核心思想是,先删除缓存中的数据,再更新数据库中的数据,然后再将新的数据写入缓存。这样,就可以保证缓存中的数据和数据库中的数据一致。
接下来,我们用一个例子来说明双删策略的执行过程。假设有两个请求 A 和 B,请求 A 进行了更新操作,请求 B 进行了查询操作。
- 请求 A 进行写操作,先删除缓存中的数据。
在这一步中,请求 A 先删除了缓存中的数据。
- 请求 B 查询缓存,发现缓存不存在。
由于请求 A 删除了缓存中的数据,因此请求 B 在查询缓存时,发现缓存中并没有请求所需要的数据。
- 请求 B 去数据库查询得到旧值。
既然缓存中没有数据,那么请求 B 就去数据库中查询请求所需要的数据,但是由于请求 A 还没有更新数据库中的数据,因此请求 B 得到的是旧的数据。
- 请求 B 将旧值写入缓存。
由于请求 B 得到的是旧的数据,因此它将旧的数据写入了缓存中。
- 请求 A 更新数据库中的数据。
在这一步中,请求 A 更新了数据库中的数据,这个数据就是我们需要的最新数据。
- 请求 A 将新值写入缓存。
现在,数据库中的数据已经更新了,请求 A 就将最新的数据写入缓存中,这样,缓存中的数据和数据库中的数据就保持了一致。
通过上述的例子,我们可以看出,双删策略的确可以解决缓存和数据库数据不一致的问题。在这个过程中,我们首先删除了缓存中的数据,保证了缓存和数据库中的数据一致性,然后再将最新的数据写入缓存,保证了读取数据的效率。当然,双删策略也有一些缺点,比如在高并发场景下可能会出现性能问题,但是,相比于数据不一致带来的问题,这些缺点都是可以接受的。
总之,双删策略是一种非常实用的解决缓存和数据库数据不一致问题的策略,它可以保证系统的高可用性和读取数据的高效性。虽然在实际应用中可能会带来一些性能问题,但是,相比数据不一致问题带来的风险,这些问题都是可以通过优化解决的。
🎉 延时双删策略
延时双删策略,这是一个用于解决数据库读写一致性问题的方案。相信很多同学都遇到过这种情况:在一个数据库读写分离架构中,一个请求进行更新操作,另一个请求进行查询操作,结果查询出来的数据却不是最新的,这就是因为数据还没来得及同步到从库,而查询请求却已经到达了从库,所以查询到的是旧值。
那么延时双删策略是怎么解决这个问题的呢?先来看看这个方案的执行步骤。
第一步,先淘汰缓存,这是因为如果不淘汰缓存,当下一次查询请求到来时,查询到的仍然是旧值,无法解决一致性问题。
第二步,将更新数据写入到数据库中,这一步和原来一样。
第三步,休眠1秒,再次淘汰缓存,这是关键步骤。1秒的时间是怎么得出来的呢?我们可以评估一下项目中读数据业务逻辑的耗时,在读数据业务逻辑的耗时基础上加上几百毫秒,就可以确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
这样一来,因为我们在数据库中写入数据后,又休眠了1秒再次删除缓存,可以将在这1秒内可能造成的缓存脏数据再次删除掉,从而解决数据库读写一致性的问题。
但是,采用这种同步淘汰策略会降低吞吐量,那么怎么办呢?这时候又要引入一个异步删除的概念,即将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后再返回,可以直接返回,从而加大吞吐量。
总结一下,延时双删策略是一种用于解决数据库读写一致性问题的方案,通过在写请求后延迟删除缓存来解决从库同步延迟带来的数据不一致问题,同时通过异步删除来提高吞吐量。希望这篇文章能够帮助到大家理解并应用这个实用的方案。
🎉 异步延时删除策略
异步延时删除策略。这个策略实际上是用来解决数据库读写操作中可能出现的数据不一致问题的。相信大家都知道,在数据库的读写操作过程中,如果不加以管理,就极有可能出现数据不一致的问题。因此,这个问题一直被广大 IT 从业者所关注。
现在,我们来看一个例子。假设有两个请求 A 和 B,其中请求 A 做查询操作,而请求 B 则做更新操作。那么,在这种情况下就有可能出现如下情形:缓存刚好失效;请求 A 查询数据库,得到一个旧值;请求 B 将新值写入数据库;请求 B 删除缓存;请求 A 将查到的旧值写入缓存。这个过程中,由于请求 B 的写数据库操作比请求 A 的读数据库操作耗时更短,因此存在一定几率,使得请求 B 的删除操作先于请求 A 的写入操作执行,从而导致脏数据的产生。
为了解决这个问题,我们可以给缓存设有效时间,不过这种方法有时并不总是可行的。因此,我们还可以采用异步延时删除策略。具体而言,在读请求完成后,我们可以将这个删除操作放到异步队列中,并在一定时间后再执行。这样,就可以保证读请求完成后再进行删除操作,从而避免了脏数据的产生。
不过,你可能会问,如果第二次删除失败怎么办呢?这是一个非常好的问题。实际上,如果第二次删除缓存失败,就会再次出现缓存和数据库不一致的问题。假设我们还是有两个请求 A 和 B,其中请求 A 进行更新操作,而请求 B 进行查询操作。首先,请求 A 进行写操作,删除缓存;接着,请求 B 查询发现缓存不存在,然后去数据库查询得到旧值;请求 B 将旧值写入缓存;请求 A 将新值写入数据库;最后,请求 A 试图去删除请求 B 写入的缓存值,但结果失效了。由此可见,如果第二次删除缓存失败,就会再次出现缓存和数据库不一致的问题。
综上所述,异步延时删除策略是一种非常有趣的技术,可以解决数据库读写操作中可能出现的数据不一致问题。当然,如果第二次删除缓存失败,就必须考虑一些其他的策略来解决数据不一致的问题。
作为一个喜欢玩游戏的程序猿,我们经常要面对一些恶心的数据库问题。比如我们现在有一张用户的信息表,里面有用户的ID、昵称、性别等等各种各样的信息。同时,我们还有一些缓存,用来存储用户信息的一些中间结果,方便查询。但是有一天,你突然发现,缓存和数据库里的数据已经不一致了!这可怎么办呢?
🎉 解决方案一:强制更新数据库数据并重试删除缓存操作
首先,我们可以强制更新数据库里的数据。比如说,有一个用户通过一个接口来修改了自己的昵称,这时候我们就需要把数据库中的昵称修改掉。但是,这样还不够,因为缓存中的昵称也需要更新,否则查询出来的结果可能还是旧的。所以我们需要把缓存也清除掉,重新查询数据库来更新缓存中的数据。但是,这里有一个大问题,就是缓存因为种种问题可能删除失败,导致数据仍然不一致。为了避免这种情况,我们可以采用一种更加可靠的方式——发送消息队列。
具体操作是,我们将需要删除的key发送至消息队列,然后自己消费消息,获得需要删除的key。接着,继续重试删除操作,直到成功为止。但是,这种方案也有一个非常明显的缺点——对业务线代码造成大量的侵入,因为我们需要在代码中加入发送消息和消费消息的逻辑。
🎉 解决方案二:订阅binlog程序+消息队列重试机制
为了避免解决方案一的弊端,在不侵入业务线代码的前提下,我们可以采用解决方案二。具体来说,我们可以启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
具体操作是,当我们在应用程序中进行了一次数据库操作(比如添加一个用户),数据库会将操作信息写入binlog日志当中。这时候,订阅程序会提取出所需要的数据以及key,并将这些信息发送至消息队列。接着,另起一段非业务代码,获得该信息,并尝试删除缓存操作。但如果删除失败,这些信息会重新被发送至消息队列,以供重试操作。
这种方案非常可靠,因为它可以保证数据一致性,同时也能保证不侵入业务线代码的情况下进行操作。订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。重试机制,采用的是消息队列的方式,如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试即可。
综上所述,对于数据库和缓存不一致的问题,我们可以采用两种不同的解决方案,分别为强制更新数据库数据并重试删除缓存操作、订阅binlog程序+消息队列重试机制。但是需要注意的是,这两种方案都有各自的优缺点,需要根据具体情况进行选择。