数据库与Redis缓存双写的问题与解决方案

数据库与Redis缓存双写的问题与解决方案

背景介绍

当项目的流量越来越大时,经常需要查询数据库,可是有一部分数据是不怎么经常变动。这个时候,就需要引入缓存,使用缓存能有一定的效果解决频繁查询数据库的问题,也能提高接口的访问性能。

在使用缓存的时,可能会出现缓存与数据库的数据不一致的问题,这就是本篇文章要写的内容。

场景

本文列了两个常见的场景:

  1. 先修改数据库,再删除缓存,如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据出现不一致。
  2. 先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。数据变更的程序完成了数据库的修改。此时,数据库和缓存中的数据不一样了。

先来讨论第一个问题

先修改数据库,再删除缓存

在数据发生变更时,可能会这么做,先去更新数据库中的数据,更新完后再去删除Redis中的缓存,等下次请求过来时,查询数据库把数据写入到缓存中。

可在这个过程中,可能会因为某些不可控的因素导致缓存写入失败,例如:网络抖动、缓存宕机、缓存服务器压力过大导致写入超时等等。

场景1

在更新库存时,先将数据库的数据给更新了,数据库中的商品库存=99,随后想要删除缓存中库存=100的数据,但它失败了,此时客户端读取到的库存为100,明显就出现了不一致的情况。

想要解决这个问题,也比较简单。

解决方案:可以先删除缓存,再修改数据库,如果删除缓存成功了,如果修改数据库失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。

失败的情况:

场景1失败

成功的情况:

场景1成功

这个比较简单的场景,通过这种先删后改的方式,就可以解决了。

删除缓存后,数据库中的数据还在更新中,此时又来一个读缓存请求

虽然解决了上面的场景,但还会出现一种情况,删除时删除了,但数据库中的数据却还在更新中,读缓存的请求发现缓存没有数据,就发送了一个读数据库的请求,此时读取库存为100,读完后数据库却更新成功了,将库存100写入了缓存中,又出现了缓存与数据库不一致的情况。

特别是在高并发下,很容易就出现这样的问题,想要解决这个问题,就需要把数据库与缓存更新与读取操作进行异步串行化。

场景2

解决方案:

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个JVM内部的队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据与更新缓存的操作,根据唯一标识路由之后,也发送同一个JVM内部的队列中。一个队列对应一个工作线程。

每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先执行,删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。

场景2解决方案

虽然解决在高并发的情况下的缓存与数据库不一致的问题,但它还有一个优化点,库存更新完后的第一个读请求已经将缓存写入了Redis中,但队列中还有一些读请求积压着。

这些读请求它会去操作,将查询到的数据写入到Redis中,这些操作却显得很多余,因为更新后的第一个读请求已经做过了,后面的读请求再去做这个操作,就有些浪费系统的资源了。

所以,可以根据唯一标识去判断一下,前面是否已经有过读请求刷新缓存的操作,没有的话就去刷新缓存,有过的话就将这个请求给过滤,不再去操作刷新缓存这一个步骤,直接将数据返回给客户端。

以上就是这篇文章想要说的两个点以及它们的解决方案,不一定很完美,也许还有一些其他更好的解决方案,但是随着时间的推移和技术经验的积累,解决这些问题的方案肯定会越来越好。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×