如何保证数据库和缓存数据的一致性

如何保证数据库和缓存数据的一致性,一般有如下六种解决方案:

  1. 先更新缓存,后更新数据库
  2. 先删除缓存后,后新数据库
  3. 先更新数据库,后更新缓存
  4. 先更新数据库,后删除缓存
  5. 先删除缓存,后更新数据库,再删除缓存
  6. 先删除缓存,后更新数据库,再延迟删除缓存

先更新缓存,后更新数据库

这个方案会遇到这种情况:缓存更新成功了,但是第二步更新数据库失败了,要回滚缓存的更新,但是Redis不支持事务回滚。这时候会导致数据不一致,该方案不可行。

先删除缓存,后更新数据库

这个方案即使更新数据库失败了也不需要回滚缓存。这种做法巧妙规避了上面方案失败回滚的问题,但是也引入了其他问题,导致数据不一致,该方案不可行。

出现问题的场景:如果删除完缓存和更新数据库这两步操作的中间,有新的请求来查询对应key的数据,那数据库中未更新的数据又会被加载到缓存中,等完成数据库更新之后就会导致数据不一致的问题。

先更新数据库,后更新缓存

这个方案会遇到这种情况:数据库更新成功了,但是第二步缓存更新失败。因为缓存不是主流程,数据库才是,所以不会因为更新缓存失败而回滚第一步对数据库的更新。此时一般的做法是重试机制,但是重试机制如果存在延时还是会出现数据库与缓存数据不一致的情况,不好处理,所以该方案不可行。

先更新数据库,后删除缓存

这个方案会遇到这种情况:更新数据库和删除缓存是两步操作,在更新数据库成功后和删除缓存成功前读到的都是旧数据,并且删除缓存可能失败。当然相对前面的几个方案,这个方案已经是缺陷最小的了。不过我们还可以针对存在的问题继续优化。

先删除缓存,后更新数据库,再删除缓存

这个方案其实跟前面的方案差不多,因为还是会出现前面方案提到的脏数据问题——在更新数据库成功后和删除缓存成功前读到的都是旧数据,不过能规避第二步删除缓存失败的问题,因为该方案是先删除缓存再更新数据库。只有在第一步和第二步之间又有查询请求,把旧的数据重新加载到缓存但是第三步删除缓存又失败,才会有问题,这种情况下旧需要重试机制,简单的做可以是重试几次,或者起一个异步线程不断重试一直到成功,甚至可以用MQ消息队列,如果删除缓存失败则把key作为消息体发送到消息队列中,然后再消费队列的消息,删除对应的key。这个方案在读写分离的数据库里还是有可能出现问题:更新主库成功了,主从复制还没完成,这时候从库读到的数据依旧是旧数据,如果这时候以上的三步都做完了,又有查询请求,那会把从库的旧数据重新加载到缓存中。所以这个方案在读写分离主从异步复制的数据库会有问题。

先删除缓存,后更新数据库,再延迟删除缓存

为了解决上面讲到的读写分离且主从异步复制的数据库存在的问题,这里第三步用延时删除缓存的方案来解决,更新数据库后,延时一小段时间然后再删除缓存,避免上面的问题。这里需要考虑的问题是延时多久呢?这个主要是要等主从数据复制完,具体时间看实际情况,一般建议1秒钟左右是够了。

小结

  1. 延时双删用比较简洁的方式实现 mysql 和 redis 数据最终一致性,但它不是强一致。
  2. 延时,是因为 mysql 和 redis 主从节点数据同步不是实时的,所以需要等待一段时间,去增强它们的数据一致性。
  3. 延时是指当前请求逻辑处理延时,而不是当前线程或进程睡眠延时。
  4. mysql 和 redis 数据一致性是一个复杂的课题,通常是多种策略同时使用,例如:延时双删、redis 过期淘汰、通过路由策略串行处理同类型数据、分布式锁等等。
  5. 如果要做到强一致性,一般是对key加锁的方式去实现的,这种效率一般会比较低,除非真的需要强一致性,否则一般不建议加锁。

 

posted @ 2024-03-02 01:23  cs_wu  阅读(650)  评论(0编辑  收藏  举报