• 如何解决缓存一致性问题


    如何解决缓存一致性问题

    引入缓存,我们的很大原因是为了让经常访问而不常修改的数据快速响应,提高系统性能。除此之外还有一些对及时性、数据一致性不高的场景。

    使用缓存我们还有一个问题就是,缓存的数据一致性问题,即保证数据库的数据与我们缓存的数据一致,如何解决,我们常用的解决方式有以下两种。

    1.双写模式

    双写就是,写入数据库的时候,也更新缓存中的数据。如果细分析下来这两个步骤不同顺序执行也会不同效果。

    数据一致性考虑主要两点:在不考虑并发问情况的异常问题,在并发情况下的不安全问题。

    不考虑并发问情况出现异常

    在不考虑并发问情况下我们考虑:

    1. 先更新缓存,后更新数据库
    2. 先更新数据库,后更新缓存

    这都可能会出现业务问题。比如:更新了缓存,但更新数据库出错了,导致不一致。那如何解决?

    解决的办法就是重试,详细在后面讲到。

    我们还能思考一层就是更新缓存和数据库的操作容易出错吗?是否需要保证这一层的高可用?我们引入缓存是为了性能,强一致性的场景是否需要缓存呢?

    并发问题情况

    而双写在并发情况下会出现以下问题

    image-20221116230614229

    如果我们设置了缓存过期,即时出现上图的脏数据问题,数据不一致,等缓存过期,重新更新缓存,最终还是得到正确的数据,叫做最终一致性。这个过程时间不是立即的,所以适用于时效性不要求严格的场景

    当然我们也可以双写的时候加锁,避免脏数据的问题。这里的锁保证数据一致。写写互斥,读和写也要互斥关系。

    缓存利用率的角度来评估这个方案,也是不太推荐的。这是因为每次数据发生变更,都更新缓存,但是缓存中的数据不一定会被马上读取,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。

    2.失效模式

    指的是更新数据时,删除缓存。这样访问的时候先从数据库查再更新缓存。

    不考虑并发问情况出现异常

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

    这和双写模式一样,解决的办法还是重试,详细在后面讲到。

    并发问题情况

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

    image-20221116230431774

    出现不一致的情况,推荐先更新数据库后删除缓存因为数据库数据保证最新,那么缓存过了有效期也会最终一致。

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

    image-20221116230535592

    这个问题也是一个脏数据问题,更新到第一个线程的数据,缓存数据不是最新的。这个问题如何解决呢?还是能够使用设置缓存过期,然后保证最终一致性。除此之外使用读写锁,把读和写锁住就能解决这个问题。

    设置缓存的过期时间。缓存中不经常访问的数据,随着时间的推移,都会逐渐「过期」淘汰掉,最终缓存中保留的,都是经常被访问的「热数据」,缓存利用率得以最大化。

    不同顺序的解决方法都一样,如下:

    • 设置缓存过期,然后保证最终一致性。
    • 加锁[读写锁]

    3.保证两步都成功

    不考虑并发问情况出现异常,如何保证两部都成功?

    使用重试,但重试需要思考下面问题

    • 立即重试很大概率「还会失败」
    • 「重试次数」设置多少才合理?
    • 重试会一直「占用」这个线程资源,无法服务其它客户端请求

    更好的是异步重试,直接把缓存的操作放消息队列上通知操作,MQ保证消息可靠,异步释放当前线程。或者订阅数据库变更日志,再操作缓存

    缓存数据一致性解决-Canal

    使用canal从MySQL的binlog获取数据,更新到缓存。

    image-20221116221708603

    4.缓存数据一致性解决方案

    无论是双写模式还是失效模式,都会导致缓存的不一致问题(脏数据)。即多个实例同时更新会出事。怎么办?

    • 如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
    • 如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
    • 缓存数据+过期时间也足够解决大部分业务对于缓存的要求。强一致性可加锁。

    如何保证强一致性

    双写模式和失效模式都一样,加上锁就能保证强一致,比如加上读写锁,通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。由于写操作是排他锁,所以会损耗一定性能,写时读就会降低并发量。我们要考虑加上了锁之后代价是否大于加上缓存的性能提升

    总结

    • 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可,这是一个保险方案。
    • 正常来说推荐失效模式使用先更新数据库。
    • 我们不应该过度设计,增加系统的复杂性。遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
    • 性能和一致性不能同时满足,性能与场景结合考虑,是否选择最终一致性,还是强一致性(加锁)。
    • 失败场景下要保证一致性,常见手段就是重试,同步重试会影响吞吐量,所以通常会采用异步重试的方案

    文章

    缓存和数据库一致性问题,看这篇就够了

  • 相关阅读:
    C#基础入门教程-字符串(String)
    Flink系列文档-(YY01)-初识Flink
    java毕业设计——基于java+jsp+Tomcat的电子书下载系统设计与实现(毕业论文+程序源码)——电子书下载系统
    Zookeeper临时节点删除时机解析
    子集生成算法:给定一个集合,枚举所有可能的子集
    R语言Sys.Date函数获取当前日期、将独立的年、月、日信息转化为对应的日期、将多个独立的年月日向量信息转化为多个日期
    基于时空RBF-NN进行非线性系统识别(Matlab代码实现)
    如何使用ReentrantLock的条件变量,让多个线程顺序执行?
    通俗易懂!一文看懂手机Root的操作与防护
    【7.29】代码源 - 【排列】【石子游戏 II】【Cow and Snacks】【最小生成数】【数列】
  • 原文地址:https://blog.csdn.net/hwh295/article/details/127895810