天天看点

Redis和Mysql如何保证数据⼀致

Redis和数据库读写一致问题,主要是由于Redis和数据库的操作不同步,也不可能做到同步导致的,那么我们所能做的,也就是尽可能保证Redis中的脏数据能够自己消除,而不是做到毫秒不差,缓存一致问题这在计算机界都是一个难题。

我们要先说清楚,Redis的更新,是直接将原来的值删除。然后再从数据库中取值时再存到数据库。为什么不直接修改Redis值呢?因为我们没有必要数据库一更新就去改Redis,不妨等到需要时再存到数据库。

什么意思呢?通俗一点,你开了一个店,里面有各类售价。在门口也有个价格表,一开始是全空的。每次有人想你问一种商品价格,你就去价格表上补充上该商品价格。当有一件商品价格变化了,你要做的,就是直接从价格表上直接删了这个商品。而不是去价格表更新这个商品。如果这个商品一个月都没人买,而价格每天都变,那你每天修改价格表就没有意义。等到一个月后,有人需要这个商品,来问价格,再把此时的价格更新到价格表,一个月就改了一次价格表,也没有耽误任何事,是不是很好!

因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题。

删除缓存,再写数据库。

如果删除了缓存Redis,还没有来得及写MySQL,另一个线程来读取,发现缓存为空,则去数据库中读取数据,此时缓存中为脏数据。

场景一:有一热门贴,楼主删除了帖子,Redis将该帖子删除,并修改数据库。此时,有人访问了该帖子,发现Redis不存在,去数据库找,并更新到Redis。此时才完成数据库的删除操作。那么帖子删除失败,仍然在Redis中可以访问。

先写数据库,再删除缓存

删除缓存失败,那么,其他线程从缓存中读取到的就是旧值,还是和实际数据库中的值不同。

场景二:如更新某商品的库存,当前商品的库存是100,现在要更新为90,先更新数据库更改成90,然后删除缓存,发现删除缓存失败了,这意味着数据库存的是90,而缓存是100,这导致数据库和缓存不一致。

为缓存设置过期时间是最终保证数据一致性的解决方案,该方案保证了任何操作都是先从数据库入手例如场景一,帖子在500ms后自动过期,那么楼主删帖有500ms延时是完全可以接受的。场景二,100在500m后过期,期间访问Redis脏数据的情况很少甚至没有。

一、 延时双删策略

        1)先删除缓存

        2)再写数据库

        3)休眠500毫秒(根据具体的业务时间来定)

        4)再次删除缓存。

这样,在写数据库时,有其他线程读了Mysql,把⽼数据读到了Redis中,只要这个时间小于500毫秒,那么500毫秒之后,也会被删除掉,从⽽把数据保持⼀致。也就是后发制人。

这个500毫秒怎么确定的,具体该休眠多久呢?

需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求才删除Redis,可以删除这次读请求造成的缓存脏数据。

当然,这种策略还要考虑 redis 和数据库主从同步的耗时。最后的写数据的休眠时间:则在读数据业务逻辑的耗时的基础上,加上几百ms即可。比如:休眠1秒。

引入了一个问题

A线程需要在更新数据库后,还要休眠M秒再次淘汰缓存,等所有操作都执行完,这一个更新操作才真正完成,降低了更新操作的吞吐量。

解决办法:用“异步淘汰”的策略,将休眠M秒以及二次淘汰放在另一个线程中,A线程在更新完数据库后,可以直接返回成功而不用等待。

二、结合双删策略+缓存过期设置

     这样最差的情况就是在脏数据未过期的500ms内(假设500ms过期)数据存在不一致,而且又增加了写数据库请求的耗时,情况更加罕见。

三、队列

慢着,为什么还有三,不是已经解决了吗?

万一两次删除Redis都失败了呢?

好吧,你牛!那怎么办?那只能花时间确定到底删除成功没有了。

Redis和Mysql如何保证数据⼀致

①更新数据库②由于各种原因缓存删除失败③将删除失败的缓存放入消息队列中④业务代码从消息队列中获取需要删除的key⑤继续尝试删除操作,直到成功

这一方法的问题从图上就可看出,所有事都是业务代码做的,要大量修改业务代码。

其他:

Redis和Mysql如何保证数据⼀致

将数据库操作存入binlog日志中,读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。

这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至中间件,由中间件根据binlog中的记录,对Redis进行删除。

其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。

这里可以结合使用canal(阿里的一款开源框架),通过该框架可以对MySQL的binlog进行订阅,而canal正是模仿了mysql的slave数据库的备份请求,使得Redis的数据更新达到了相同的效果。

当然,这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis。