当前位置: 首页 > 后端技术 > Java

Redis缓存一致性问题

时间:2023-04-01 22:17:18 Java

在高并发的情况下,如果删除了缓存,此时更新数据库,但是还没有更新完成,又发起了一次请求查询数据。如果没有缓存,则去数据库里查,以商品库存为例,如果数据库中商品库存为100,则查询到的库存为100,然后插入缓存。插入缓存后,原来更新数据库的线程将数据库更新为99,导致缓存不一致的情况。这种情况怎么解决比较好?本文主要讨论以下三个部分:1.讲解缓存更新策略2.分析每种策略的不足之处3.针对不足之处提供改进方案。操作缓存和数据库主要有两种方式:1.先删除缓存,然后更新数据库先删除缓存。数据库尚未成功更新。如果此时读取缓存,缓存不存在,从数据库中读取旧值,缓存不一致。这个方案似乎没问题。用于先操作缓存(删除),再操作数据库。如果删除缓存成功,则更新数据库失败。缓存中没有数据,之前的数据在数据库中。数据没有不一致,对业务没有影响。只是下次读的时候,会多一个cachemiss。但是如果放在并发场景下,一个写请求过来,删除缓存,准备更新数据库(更新还没完成)。然后一个读请求过来,缓存未命中,从数据库中读取旧数据,重新放入缓存。至此,数据库更新完成。此时的情况是缓存中有旧数据,数据库中有新数据,数据不一致的问题就会凸显出来。对于这种场景下的问题,可以采用延迟双删策略来解决。延迟双删解决方案的思路是防止其他线程在更新数据库时从缓存中读取数据。更新数据库后,休眠一会,然后再次删除缓存。休眠时间要根据业务读写缓存时间来评估,休眠时间要长于读写缓存时间。过程如下:线程1删除缓存,然后更新数据库。线程2读取缓存,发现缓存被删除了,于是直接从数据库中读取。此时线程1还没有更新,所以读取的是旧值。然后将旧值写入缓存线程1。根据预估时间,sleep,因为睡眠时间大于线程2读数据+写缓存时间,所以再次删除缓存。如果有其他线程要读取缓存,它会再次从数据库中读取最新的值。2、先更新数据库,再删除缓存既然上面说的先删除缓存是行不通的,那如果反过来操作,先更新数据库,再删除缓存呢?这是一个更明显的问题。如果数据库更新成功,如果删除缓存失败或者没有及时删除,那么其他线程会从缓存中读取旧值,仍然会出现不一致的情况。那么如何处理这种情况呢?基于binlog日志和消息队列:应用直接向数据库写入数据。数据库更新binlog日志。使用Canal中间件读取binlog日志。Canal借助限流组件按频率向MQ发送数据。应用程序监听MQ通道,将MQ数据更新到Redis缓存中。可以看出这个方案对于开发来说是比较轻量级的,不需要太在意缓存级别。这个方案虽然比较重,但是很容易形成一个统一的方案。3.总结首先我们要明确一点,缓存不是更新,而是删除。删除缓存有两种方式:先删除缓存,再更新数据库。解决方案是使用延迟双删除。先更新数据库,再删除缓存。解决办法是同步消息队列或者其他binlog。消息队列的引入会带来更多的问题,不建议直接使用。为什么删除而不是更新?我们以先更新数据库再删除缓存为例。如果是更新,就是先更新数据库,再更新缓存。比如:1小时内数据库更新1000次,那么缓存也会更新1000次,但是缓存1小时内可能只读一次,那么这1000次更新有必要吗?反之,如果删除,即使数据库更新1000次,那么也只做一次缓存删除,真正读取缓存时才加载数据库。对于缓存一致性要求不是很高的场景,只设置超时时间即可。其实如果并发不是很高,不管是选择先删除缓存还是后删除缓存,这种问题都很少会出现,但是在高并发下,应该知道如何解决。最后,我个人认为没有完美的解决方案,总要牺牲一些东西。