zoukankan      html  css  js  c++  java
  • 缓存与数据库的一致性

    https://blog.csdn.net/huazhongkejidaxuezpp/article/details/88945627

    背景
               cache如memcache,redia等缓存来缓存数据库读取出来的数据,以提高读性能。但凡是使用缓存的项目,几乎都会遇到一个普遍的问题: 在不断增删改数据的过程中,如何保持缓存与数据库中数据的一致性。在支付、下单类业务中,此类问题尤为普遍。下面就自己对此的一些理解。浅谈一下自己的看法。

    缓存的衡量指标
    命中率、响应时间;缓存一致性。

    命中率:请求的命中率与miss率。可以通过一定的工具监控到。

    响应时间:包括命中时的响应时间,非命中时的响应时间。

    缓存一致性:在不断进行增删改操作后,缓存中的数据是否与数据库数据保持一致,避免二者不一致,获取数据不正确的情况发生。

    使用缓存可能带来的问题
             分布式环境下非常容易出现缓存和数据库间的数据一致性问题。在使用缓存前,如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存。我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。

             经典的几个问题如下:

    1)缓存和数据库间数据一致性问题

    问题描述:缓存和数据库间的数据不一致。缓存与数据库不一致的情况,大致分为三类:

    数据库有数据,缓存没有数据;

    数据库有数据,缓存也有数据,数据不相等;

    数据库没有数据,缓存有数据。

    合适的策略:包括 合适的缓存更新策略,更新数据库后要及时更新缓存、缓存失败时增加重试机制,例如MQ模式的消息队列。

    2)缓存击穿问题

    问题描述:缓存击穿表示恶意用户模拟请求很多缓存中不存在的数据,由于缓存中都没有,导致这些请求短时间内直接落在了数据库上,导致数据库异常。

    场景举例:抢购活动、秒杀活动的接口API被大量的恶意用户刷,导致短时间内数据库宕机了

    解决方案:

    使用互斥锁排队
    业界比较普遍的一种做法,即根据key获取value值为空时,锁上,从数据库中load数据后再释放锁。若其它线程获取锁失败,则等待一段时间后重试。这里要注意,分布式环境中要使用分布式锁,单机的话用普通的锁(synchronized、Lock)就够了。

    从一定程度上减轻数据库压力,但是锁机制使得逻辑的复杂度增加,吞吐量也降低了,有点治标不治本

    布隆过滤器(推荐)
    bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小

    3)缓存雪崩问题

    问题描述:缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。

    解决方案:

    加锁排队
    建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,并且更新A缓存和B缓存;
    4)缓存并发问题

    问题描述:多个redis的client同时set key引起的并发问题

    解决方案:

    把redis.set操作放在队列中使其串行化,必须的一个一个执行
    加锁
    缓存使用/更新套路
    1. 首先尝试从缓存读取,读到数据则直接返回;如果读不到,就更新数据库,并将数据会写到缓存,并返回。

    2. 需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)。

    进一步思考:
    先删除缓存,然后再更新数据库:如果A,B两个线程同时要更新数据,并且A,B已经都做完了删除缓存这一步,接下来,A先更新了数据库,C线程读取数据,由于缓存没有,则查数据库,并把A更新的数据,写入了缓存,最后B更新数据库。那么缓存和数据库的值就不一致

    为什么最后是把缓存的数据删掉,而不是把更新的数据写到缓存里。这么做引发的问题是,如果A,B两个线程同时做数据更新,A先更新了数据库,B后更新数据库,则此时数据库里存的是B的数据。而更新缓存的时候,是B先更新了缓存,而A后更新了缓存,则缓存里是A的数据。这样缓存和数据库的数据也不一致。

    效果:理论上也是有不一致的风险的,但概率很小。不一致原因:产生的原因是更新数据库成功,但是删除缓存失败。可以进一步考虑的方案:

    1. 对删除缓存进行重试,数据的一致性要求越高,我越是重试得快。

    2. 定期全量更新,简单地说,就是我定期把缓存全部清掉,然后再全量加载。

    3. 给所有的缓存一个失效期。失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定

    进一步思考,当更新数据库时候,缓存应该如何更新  
    更新缓存VS淘汰缓存

         答:更新缓存很直接,但是涉及到本次更新的数据结果需要一堆数据运算(例如更新用户余额,可能需要先看看有没有优惠券等),复杂度就增加了。而淘汰缓存仅仅会增加一次cache miss,代价可以忽略,所以建议淘汰缓存。 当然了可以采用更新缓存而不是淘汰缓存,前提是更新的代价比较低

    测试思路
              测试缓存的时候,除了业务在正常情况下需要正常交互外,尤其是热点key过期的时候,需要测试击穿,以及雪崩,穿透等情形对下游DB的并发请求带来的影响。

              测试场景:

    1、命中情况下:响应情况

    2、并发下、非命中下响应情况

    思考点

    测试点

    (雪崩)缓存会不会集中失效?如会,怎么避免?

    1、 (如果会失效)模拟集中失效场景

    2、查看服务响应情况

    3、如果有措施,测试措施的效果。

    措施例如:

    在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
    缓存key是什么?为什么?

    1、key的内容

    2、缓存占用的大小

    会不会出现缓存穿透?如何避免?

    缓存穿透是指查询一个一定不存在的数据

    1、模拟穿透情况

    2、查看服务响应情况

    3、测试应对方案:

    1)使用互斥锁

    2)从数据库找不到的时候,我们也将这个空对象设置到缓存里边去

    3)布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层

    如果缓存挂了,业务是否会有影响?怎么控制这里的影响面?

    1、模拟缓存挂了的情况

    2、查看服务响应情况

    3、测试应对缓存挂掉的方案:

    事发前:实现redis的高可用,尽量避免缓存全部挂掉

    事发中:万一redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),确保业务正常

    事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

    什么时候更新缓存?如何确保更新成功?

    1、模拟业务(并发)各种增删改查的情况

    2、检查缓存更新情况

    3、测试不同更新策略

    ps-两种策略各自有优缺点:

    先删除缓存,再更新数据库:在高并发下表现不如意,在原子性被破坏时表现优异;
    先更新数据库,再删除缓存(Cache Aside Pattern设计模式):在高并发下表现优异,在原子性被破坏时表现不如意
    失效时间是多少?为什么?

    1、失效时间设置多少

    2、失效后影响

    缓存方案命中率/miss率多少

    1、模拟缓存使用场景

    2、评估命中率/miss率

    3、使用工具监控命中率/miss率

  • 相关阅读:
    【软件测试】软件缺陷粗浅认识及白盒测试举例
    【软件测试】等价类划分
    【软件测试】对本门课程粗浅理解
    阿里云服务器本地ping超时,远程可以正常ping通
    不忘初心
    开源框架、控件、组件、插件记录
    Flex中窗口可随意拖拽功能实现
    初探数据类型相关问题
    [TSCTF-J 2021] 解题报告
    指针
  • 原文地址:https://www.cnblogs.com/cuiqq/p/12005972.html
Copyright © 2011-2022 走看看