当前位置: 首页 > 后端技术 > Java

Redis缓存穿透、击穿、雪崩、数据库与缓存一致性

时间:2023-04-01 23:29:31 Java

Redis作为一个高性能的非关系型(NoSQL)键值对数据库,被大量用户喜爱和使用,大家在使用中项目中Redis用于数据缓存,但是在使用过程中有一些问题需要我们考虑。典型的问题有:缓存穿透、缓存雪崩、缓存击穿、与关系数据库的一致性。一、缓存穿透1、概念缓存穿透是指查询缓存或数据库中不存在的数据。正常使用缓存的流程大致是数据查询先缓存,如果key不存在或者key已经过期,再查询数据库,将查询到的对象放入缓存。如果数据库查询对象为空,则不会放入缓存。大致流程如下图所示:在一些特定场景下,例如:秒杀活动。同时会有大量的请求,都是在杀同一个产品。这些请求同时检查缓存中没有数据,然后同时访问数据库。结果是一场悲剧。数据库可能会承受不住压力,直接挂掉。也会有恶意请求。一般我们的主键ID都是无符号自增类型。有些人想破坏你的数据库,对每个请求都使用负ID,而数据库中没有负ID的记录。每次都会查询数据库,每次查询都是空的,不会每次都缓存。如果有恶意攻击,可以利用该漏洞对数据库进行压力,甚至碾压数据库。3、解决方案1)验证拦截接口层进行验证,比如识别用户权限,对id等字段做基础验证,比如直接拦截id<=0的字段。2)布隆过滤器我们可以预先在过滤器中加入真实正确的商品id,每次再次查询时,先确认要查询的id是否在过滤器中,如果不在则说明该id是非法的id,不需要后续查询步骤。布隆过滤器是一种比较独特的数据结构,存在一定的错误。Bloomfilter的特点就是说不存在就肯定不存在。如果它说存在,则数据可能实际上不存在。它最大的优点是性能高,占用空间小。3)缓存空对象当存储层miss时,即使返回的空对象也会被缓存,同时设置一个过期时间,以后访问时从缓存中获取数据,保护后端数据源。但是这种方法有两个问题:如果可以缓存空值,就意味着缓存需要更多的空间来存储更多的key,因为空值的key可能有很多;如果为该值设置了过期时间,缓存层和存储层的数据在一段时间内仍然会存在不一致,影响需要保持一致性的业务。二、缓存击穿1、缓存击穿的概念是指数据不在缓存中,而是在数据库中,并且某个key非常热。它不断承载着大并发,大并发集中在访问这个点上。当key缓存时间到期后,持续的大并发会突破缓存,直接请求数据库,导致数据库不堪重负。2、解决方法1)设置热点数据永不过期。这种方法比较粗糙。如果你的热点数据对实时性要求不高,可以设置热点数据在热点期间不过期,在访问的低峰期过期,比如每天早上过期。2)可以使用分布式锁和互斥锁来控制线程访问查询数据库,但是这种方案会导致系统吞吐量下降,需要根据实际情况使用。publicstaticStringgetData(Stringkey)throwsInterruptedException{//从Redis查询数据Stringresult=getDataByKV(key);//参数验证if(StringUtils.isBlank(result)){try{//获取锁if(reenLock.tryLock()){//去数据库查询result=getDataByDB(key);//检查if(StringUtils.isNotBlank(result)){//插入缓存setDataToKV(key,result);}}else{//休眠Thread.sleep(100L);结果=getData(键);}}finally{//释放锁reenLock.unlock();}}返回结果;集中式缓存一段时间失效,导致所有请求都跑到数据库,造成数据库压力过大甚至宕机。与缓存击穿不同,缓存击穿是指并发查询同一条数据,而缓存雪崩是指不同的数据都过期了,很多数据都找不到了,所以去查数据库。缓存集中失效的原因:1)、redis服务器宕机。Redis集群大面积故障;缓存失效,此时仍有大量请求访问Redis缓存服务器;大量Redis请求失败后,这些请求会访问数据库;2.为缓存数据设置相同的过期时间,导致缓存在一定时间内集中失效。2、解决方案1)实现Redis的高可用【事前】搭建Redis哨兵(Sentinel)或者Redis集群(Cluster)可以实现高可用;当服务出现问题时,我们如何确保服务仍然可用。Hystrix在国内应用广泛,通过熔断、降级、限流三种手段减少雪崩后的损失。只要数据库没死,系统总能响应请求。【后记】Redis备份与快速预热:Redis数据备份与恢复,缓存快速预热。2)缓存数据的过期时间随机设置,防止大量数据同时过期。(1)使用不同品类的产品,缓存不同的周期。同一类别的项目,加上一个随机因素。这样就可以尽可能的分散缓存过期时间。而且,热门品类的商品缓存时间较长,冷门品类的商品缓存时间较短,也可以节省缓存服务的资源。(2)如果缓存数据库采用分布式部署,则将热点数据均匀分布在不同的缓存数据库中。(3)设置热点数据永不过期。4.数据库与缓存的一致性使用缓存可以减少时间消耗,提高系统吞吐性能。但是,有了缓存,就会有数据一致性的问题。1、Bypasscache模式一般我们使用cache,就是bypasscache模式。它的特点是读时插入缓存,写时删除缓存。1)读取请求流程如下:读取时,先读取缓存,缓存命中则直接返回数据;如果缓存没有命中,则读取数据库,从数据库中取出数据,放入缓存中,同时返回响应。2)写入过程如下:这里有两个问题需要思考:1)为什么写入请求需要删除库存而不是插入缓存?2)为什么先操作数据库再删除旧缓存?你能颠倒顺序吗?2.删除缓存,还是更新缓存?我们在操作缓存的时候,是删除缓存还是更新缓存呢?先看一个例子:线程A先发起写操作,先更新数据库;线程B再次发起写操作,第二步更新数据库;由于网络等原因,线程B先更新缓存;线程A更新缓存。此时缓存保存的是A的数据(旧数据),数据库保存的是B的数据(新数据),数据不一致,出现脏数据。如果删除缓存而不是更新缓存,就不会出现这种脏数据问题。3、在先操作数据库还是先缓存的情况下,在双写的情况下,应该先操作数据库还是先操作缓存?我们再看一个例子:假设有两个请求A和B,请求A执行更新操作,请求B执行查询和读取操作。线程A发起写操作,第一步是delcache;此时,线程B发起读操作,缓存未命中;线程B继续读DB,读出一个旧数据;然后线程B将旧数据设置到缓存中;线程A写入DB中的最新数据;这是一个问题,缓存中的数据和数据库不一致。缓存存储旧数据,数据库存储新数据。所以Cache-Aside缓存模式选择先操作数据库,而不是先操作缓存。声明:公众号本文转载请至文首。声明是去公众号:BackendMetaverse。尊重作者的辛勤劳动成果。同时也可以向我索要文章的markdown原稿和原图。其他情况严禁转载!关注公众号:后端元宇宙。持续输出优质好文