大家好,我是楼仔!很久以前就遇到过这个问题,但是没有仔细研究过。上个月看了Geek的课程,有一篇文章专门讲解。正好有粉丝也问过我这个问题,所以觉得有必要单独讨论一下。出一篇文章。之前看了很多相关的文章,但是感觉都不好。很多文章都会讲各种策略,比如(bypasscaching)策略,(read-through/write-through)策略和(write-back)策略等等,感觉真的没什么意思,有的文章只讲了一部分情况,也不要说出最优解。我直接抛出结论:在满足实时性的情况下,没有完全保持两者一致性的方案,只有最终一致性方案。根据网上很多解决方案,我们总结了六种,直接看目录:不好的解决方案1.先写MySQL,再写Redis图解说明:这是一个时序图,描述调用请求的顺序;橙色线是requestA,黑色线是requestB;橙色文字为MySQL与Redis最终不一致的数据;数据从10更新到11;下面的图都是这个意思,这里就不再赘述了。请求A和B都是先写MySQL,再写Redis。在高并发的情况下,如果请求A在写Redis的时候卡了一会,而请求B已经顺序完成了数据更新,就会出现图中的问题。这张图已经画的很清楚了,就不再赘述了,但是这里有一个前提,就是对于读请求,先读Redis,如果没有再读DB,但是读请求不会写回Redis。说白了就是读请求不会更新Redis。2、先写Redis,再写MySQL同“先写MySQL,再写Redis”,看图秒懂。3、先删除Redis,再写MySQL。这张图和上面那张有点不一样。前面的请求A和B都是更新请求。这里,请求A是更新请求,而请求B是读请求,请求B的读请求会写回Redis。请求A先删除缓存。数据可能因为滞后没有更新到MySQL,导致两者数据不一致。这种情况出现的概率比较高,因为请求A更新MySQL可能需要很长时间,而请求B前两步是查询,会很快。好的解决方案4.先删除Redis,再写MySQL,再删除Redis。对于“先删除Redis,再写MySQL”,如果要解决最后的不一致问题,其实可以重新删除Redis。这就是大家常说的“Cache双删”。为了方便大家看图,对于蓝色的文字,“deletecache10”一定在“writebackcache10”的后面,那怎么保证一定在后面呢?网上给出的第一个方案是请求最后删除A,等待500ms。这种方案,看看就好,反正不知道怎么用,太low了,风险不可控。有更好的解决方案吗?我建议异步串行删除,即删除请求排队:异步删除对线上业务无影响,串行处理保证并发情况下正确删除。双删失败怎么办?网上有计划给Redis加一个缓存过期时间。我不同意这一点。我个人建议使用整个重试机制。可以使用消息队列的重试机制,也可以使用整张表来记录重试次数。有很多方法。小结:“缓存双删”不要用无脑sleep500ms;通过异步&串行消息队列,实现最后一次缓存删除;如果缓存删除失败,则增加重试机制。5、先写MySQL,再删除Redis。对于上述情况,第一次查询,请求B查询到的数据是10,而MySQL的数据是11,只有这个不一致。对于那些不需要强一致性的业务是可以容忍的。(那么在什么情况下是不能容忍的,比如秒杀业务,库存服务等)请求B进行二次查询时,因为没有命中Redis,所以会再次查DB,然后回写到里德斯。这里需要满足两个条件:缓存刚刚自动过期;请求B从数据库中查出10,写回缓存的时间比请求A写数据库删除缓存的时间长。对于第二种情况,我们都知道更新DB肯定比查询时间长,所以出现这种情况的概率很小,满足上述条件的情况就更小了。6、先写MySQL,通过Binlog异步更新Redis。该方案主要是监控MySQL的Binlog,然后异步更新数据到Redis。该方案有个前提,查询请求不会写回Redis。这种方案会保证MySQL和Redis的最终一致性,但是如果请求B中间需要查询数据,如果缓存中没有数据,会直接查询DB;如果缓存中有数据,查询数据也会不一致。因此,该方案是实现最终一致性的最终方案,但不能保证实时性。几种方案对比下面我们对比一下上面讨论的六种方案:(1)先写Redis,再写MySQL。我绝对不会使用这个方案。万一DB挂了,你把数据写到缓存里,DB就没有数据了。这是灾难性的;以前见过同学这样用的。如果写DB失败,在Redis上进行逆向操作,那么逆向操作失败,是否要做重试?(2)先写MySQL,再写Redis。很多对并发和一致性要求不高的项目都是采用这种方式。我以前经常这样做,但不建议这样做;报警,线下处理。(3)先删除Redis,再写MySQL。我以前从未使用过它,所以请忽略它。(4)先删除Redis,再写MySQL,再删除Redis。这种方法虽然可行,但是感觉很复杂,异步删除Redis需要一个消息队列。(5)先写MySQL,再删除Redis。推荐使用此方法。如果删除Redis失败,可以多试几次,否则会报警;该解决方案是实时性能最好的解决方案。在一些高并发场景下,推荐这个。(6)先写MySQL,通过Binlog异步更新Redis。对于异地容灾、数据汇总等,推荐使用这种方式,比如binlog+kafka,数据一致性可以达到秒级;对于纯粹的高并发场景,不推荐使用这种方案,比如抢购、秒杀等。个人总结实时一致性方案:采用“先写MySQL,后删除Redis”的策略。一致的最优解。最终一致性方案:“先写MySQL,通过Binlog异步更新Redis”,通过Binlog结合消息队列异步更新Redis,是最终一致性的最优方案。
