• Redis并发下的cache misses处理--并发排他


    目录

    1.Read模式:

    1.布隆过滤器:缓存穿透

    2.并发排他 

    3.小总结:

    2.Write模式:双写一致性 

    3.Redis分布式锁:

    4.Redis缓存存什么数据:

    参考文章:


    Cache-aside模型包括两种模式:read模式和write模式

    1.Read模式:

    Note:查询不能保证强一致性。

    下面讲述这种模式下会遇到的问题,以及解决方案。

    并发读缓存可能遇到的问题缓存穿透,缓存击穿,雪崩,预热,都是Redis缓存里没有对应数据的情况,这种时候如果发生并发读怎么办?分两种情况:

    • Redis&DB都没有数据(缓存穿透),使用布隆过滤器,将数据库中的数据通过多次hash,以bit的方式存储到布隆过滤器的bitMap数组,并发请求过来的时候,请求的数据也要经过这样的多次hash,找到对应的多个bit,如果都是1,则认为数据库中是存在的,放行;如果有任何一个bit为0,则直接返回,找不到数据。放行的请求的请求数据有可能在数据库中也是没有的(hash碰撞,但是这种情况很少),那么则在Redis 缓存中设置value为null(一定要加过期时间,因为,万一数据库中有数据了呢???)。这样两种办法结合,就能很好的解决缓存穿透问题。
    • Redis中没有数据,但是DB中有(缓存击穿、雪崩、预热失败的情况),这个时候并发来了,需要采用并发排他的方式来处理。

    1.布隆过滤器:缓存穿透

    举例:系统数据库中商品编号为1到100,我们可以为商品创建一个布隆过滤器,然后将商品编号1到100都添加到布隆过滤器中,前端查找商品的时候,使用商品编号去布隆过滤器中查找

    • 如果布隆过滤器中存在该商品编号,直接查询redis缓存,如果布隆过滤器出现误判(概率很小),在redis中不存在,并且在数据库中也不存在(缓存穿透),则设置 set key null,并且一定要设置过期时间,万一后面增加了这种商品呢?
    • 如果不存在,直接返回找不到商品

    具体示例代码如下图所示:设置了误判率为%1

    布隆过滤器在项目中的使用流程:

    如果商品信息在数据库中被删除了,怎么办?

    2.并发排他 

    现在我们单就缓存击穿和预热的情况(缓存里没有数据,但是数据库中有数据)来研究一下。

    并发排他说白了就是并发请求来了,但是Redis缓存中没有数据,这时候应该只让一个线程去数据库拿数据,然后放入缓存,然后其它线程从缓存中取数据。

    下面以我们在实际项目中遇到的问题,来讲述一下并发排他

    预热:

    为了达到预期的并发处理能力,我们在查询的时候采用Redis缓存,但是一开始Redis里并没有缓存对应的数据,我们需要先将数据从数据库中取出,然后存储到Redis中,在高并发情况下应该怎么处理?所有请求都去数据库取数据,然后存储到Redis中?显然这是不合理的,只要由一个线程(请求)去访问数据库,将获取的数据存入Redis,其它请求只需要从Redis中获取就好了

    要达到上述效果,就需要加锁,如果系统做了集群,那么就要考虑使用分布式锁,正好Redisson为我们提供了分布式锁功能,就用它了。

    下面这段代码就是用了Redis分布式锁保证只有一个线程去访问数据库并将结果存入Redis。

    但是能看出来这里有什么问题吗???

    在并发的情况下,如果有几百个请求在同一时刻打过来,同时发现缓存里没有数据,那肯定不能都去数据库里取数据,然后再写入缓存,只需要一个线程去做这件事情就行了。

    这一个线程拿到锁,然后从数据库拿到数据,并且存入Redis缓存,然后其它的请求从Redis缓存里取数据就行了。

    其它请求不用每次都在那里(while循环里)等着拿锁,在拿锁之前先看看能不能从Redis缓存里取出数据(第一个线程大概率已经写入了),如果有Redis数据,我不用再去拿锁了,拿锁的话线程要在这里干等着。

    直接能从Redis里拿到数据,我还去拿锁干什么?正常情况下,一个线程(请求)拿锁然后把事情做了(数据写到Redis缓存),其它请求不需要拿到锁之后才能从Redis缓存里取,可以在拿锁之前取一次,一般就取到了。

    现在是每个请求都要拿锁之后才能从Redis取数据,这是一个挨着一个,排队取。while循环里就只有拿锁的操作,拿到锁之后才能继续从Redis里获取数据。在while循环里,我拿锁之前,先获取一次Redis缓存数据,不要先去获取Redis分布式锁。

    代码应该改成这样:

    3.小总结:

    2.Write模式:双写一致性 

    在需要修改数据的时候,怎样保证数据库和Redis缓存中数据一致性???

    补充:先写数据库,再删缓存,可能发生删除缓存失败的情况,为了应对这种情况,缓存要设置过期,还有就是可以使用延迟双删 

    类似的理论:拿java中的volatile举例说明,其实就是无缓存性质保证数据的一致性。

    下面是双写的四种方案的介绍:

     

    为什么说违背了cache的意义?这样解释:缓存是为了提高读性能的,写的时候没有必要一定要写缓存,因为写缓存之后,没有并发去读,这是浪费Redis内存。当然了,如果确定后续会接着发生并发读,那么可以考虑写入数据库并写入缓存。 

    canal不知道知道哪些数据是热点数据,需要放入Redis缓存。 

     以上3种方案因为各种各样的原因,都被否定了,只剩先写数据库后删缓存这一种方案。目前我们用的是这种方案。

    不过它也有自己的问题,例如删除失败,等待过期的这段时间内,数据是不一致的,所以无法保证强一致性,但是能保证最终一致性(过期后,从数据库拿数据放入缓存),符合CAP中的AP。

    3.Redis分布式锁:

    redLock红锁,解决单点故障问题:

    例如3个互相之间没有联系的Redis实例

    Zookeeper分布式锁比红锁更好用:

    4.Redis缓存存什么数据:

    Redis也可以做为存储store,存储全量数据,但是如果做为缓存,那么肯定不需要存储全量数据,只存储静态字典数据,热点数据等。

    缓存是存储热数据的,不是什么数据都要往redis存。

    如果Redis缓存快满了,导致LRU比较频繁,那么会导致redis性能急剧下降。

    参考文章:

    Cache-Aside pattern - Azure Architecture Center | Microsoft Docs

  • 相关阅读:
    python学习笔记(09)---(正则表达式)
    《全网首发》平衡三进制图灵机的构建
    HC32F4A0 以太网调试问题(测试发现各种逻辑问题)
    FFmpeg工具使用集
    vtk之【vtkPolyData、vtkCell、vtkPoints】
    Hadoop
    Springboot毕设项目个性化学习推荐网站ua750(java+VUE+Mybatis+Maven+Mysql)
    矩阵与拉普拉斯展开定理求解多元一次方程组
    解密Prompt系列22. LLM Agent之RAG的反思:放弃了压缩还是智能么?
    C与CPP常见编译工具链与构建系统简介
  • 原文地址:https://blog.csdn.net/wdquan19851029/article/details/126667562