redis小计

nosql数据库

用来缓解底层数据库压力

内存型数据库,用来做缓存,通常用来缓存前台经常性查询且不用频繁变动的数据

1、缓存穿透

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

2、处理缓存穿透:

(1)布隆过滤:待定

(2)缓存空对象:

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;

但是这种方法会存在两个问题:

如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

3、缓存雪崩

就以redis为例,当redis服务器宕机或者缓存失效,就会造成大量请求直接去访问底层数据库,数据库压力直线上升,可能会导致存储层挂掉。

处理方法:

(1)搭建redis集群,redis高可用

(2)限流降级:当缓存数据库宕机后,从存储层上保护数据库,通过锁和队列限制来减轻存储层压力,比如key值设定只能让一个线程来访问,其余的线程等待。

(3)数据预热:提前将需要大量访问的数据请求一次,将缓存提前存到redis中,并设置合适的缓存时间,让缓存失效的时间点尽量均匀。

(4)设置缓存永不失效(理想化)

(5)错峰:设置不同key值缓存失效时间相互错开

4、缓存击穿

即缓存中一个热点key值失效,在该数据还没有更新到缓存中的这段时间内,所有请求该数据的请求都会落到存储层,这将会对数据库造成巨大的压力。此外,当这些请求查询完成后,都会重复更新缓存。

解决方法:

(1)锁:互斥锁,此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。

(2)永不失效:可以考虑

互斥锁 (mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好的降低后端存储负载并在一致性上做的比较好。
” 永远不过期 “:这种方案由于没有设置真正的过期时间,实际上已经不存在热点 key 产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

5、数据一致性

1. Redis里的数据不立刻更新,等redis里数据自然过期。然后去DB里取,顺带重新set redis。这种用法被称作“Cache Aside”。好处是代码比较简单,坏处是会有一段时间DB和Redis里的数据不一致。这个不一致的时间取决于redis里数据设定的有效期,比如10min。但如果Redis里数据没设置有效期,这招就不灵了。

2. 更新DB时总是不直接触碰DB,而是通过代码。而代码做的显式更新DB,然后马上del掉redis里的数据。在下次取数据时,模式就恢复到了上一条说的方式。这也算是一种Cache Aside的变体。这要做的好处是,数据的一致性会比较好,一般正常情况下,数据不一致的时间会在1s以下,对于绝大部分的场景是足够了。但是有极少几率,由于更新时序,下Redis数据会和DB不一致(这个有文章解释,这里不展开)。

第一种简单的说就是,设置过期时间,然后去数据库读取数据,更新cache

第二种从代码层次更新数据库的时候,删除cache,从新获取cache。

一般情况下是两者结合起来使用,可以应对极大多数情况

至于别的策略本人学艺不精,等我get到了,在补充

缓存有三大矛盾:

  1. 缓存实时性和一致性问题:当有了写入后咋办?
  2. 缓存的穿透问题:当没有读到咋办?
  3. 缓存对数据库高并发访问:都来访问数据库咋办?

第一个也就是本问题。而解决这三大矛盾的刷新策略包括:

  1. 实时策略——用户体验好,是默认应该使用的策略;
  2. 异步策略——适用于并发量大,但是数据没有那么关键的情况,好处是实时性好;
  3. 定时策略——并发量实在太大,数据量也大的情况,异步都难以满足的场景;

实时策略是最常用的策略,也是保持实时性最好的策略:

  • 读取的过程,应用程序先从 cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。如果命中,应用程序从 cache 中取数据,取到后返回。
  • 写入的过程,把数据存到数据库中,成功后,再让缓存失效,失效后下次读取的时候,会被写入缓存。

从用户体验的角度,应该数据库有了写入,就马上废弃缓存,触发一次数据库的读取,从而更新缓存。

然而,这和第三个问题(高并发)就矛盾了——如果所有的都实时从数据库里面读取,高并发场景下,数据库往往受不了。

preview方法都可行,,看具体的业务选择合适的法案,大多数业务从数据实时性来说都会选择在跟新数据库的同时处理缓存,即上图,我们要评估一下更新的代价大不大,比如拿到数据更新到 Redis 需要经过很多表的关联查询,或者多个接口的调用查询,经过大量计算才能得到数据的话,就不要使用更新 Redis 的方案了,因为面对这么复杂的更新流程,不如直接删掉方便。

 

主流的方案是选择删除 Redis

(1)先更新数据库,在删除redis

我们如果更新数据库成功,删除 Redis 失败,那么 Redis 里存放的就是一个旧值,也就是删除缓存失败导致缓存和数据库的数据不一致了。

通过监听 binlog 的变化来删除缓存。我们更新数据库会产生 binlog,我们可以用一个服务监听数据库 binlog 的变化,异步去删除缓存,阿里开源的 canal 就是可以监听 binlog 的工具,只要数据发生变化就可以删除 Redis 的数据。

(2)先删除redis,在更新数据库

我们如果删除 Redis 缓存成功,更新数据库失败的话,因为我们是以数据库为准,再查一次就可以了,这个方案看似很理想,感觉没什么问题,其实在并发下也可能产生数据一致性问题。

要解决以上的问题,我们我们一般有如下的可选方案:

1、并发下多线程操作能不能让同一个操作里的 2 个步骤同时操作完成,下一个线程才能执行,让各个线程排队操作,有人说加锁,如果一个服务部署到了多个机器,就变成了分布式锁,或者是分布式队列按顺序去操作数据库或者 Redis,带来的副作用就是数据库本来是并发的,现在变成串行的了,所以这个方案看起来不太可行,加锁或者排队执行的方案降低了系统性能。

2、另外一种放啊就是延时双删的方案,也就是先删除缓存,再更新数据库,当更新数据后休眠一段时间再删除一次缓存。

伪代码如下:

redes.del(key);
db.update(data);
Thread.sleep(2000);
redes.del(key);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值