Redis缓存常见问题及淘汰策略

一、缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户(黑客)不断发起请求。

比如:数据库的 id 从 1 自增的,如果发起 id=-1 的数据,这样的不断攻击导致数据库压力很大,严重会击垮数据库。

解决

1.缓存null值,设置过期时间

2.在接口层增加校验,比如参数做校验,不合法的校验直接 return,id 做基础校验,id<=0 直接拦截

3.布隆过滤器(Bloom Filter),利用高效的数据结构和算法快速判断出这个 Key 是否在数据库中存在,不存在return,存在就去查 DB 刷新 KV 再 return。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。

布隆过滤器原理简单来说就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀。 向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。

向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个key 不存在。如果都是 1,这并不能说明这个key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会降低。

二、缓存击穿

缓存击穿是指在高并发下,一个 Key 非常热点,在不停地扛着大量的请求,高并发集中对这一个点进行访问,当这个 Key 在失效的瞬间,持续的高并发直接落到了数据库上,就在这个 Key 的点上击穿了缓存,或者当一个冷数据Key因为某件事突然热门,此时缓存中没有该Key,也会导致瞬间持续的高并发直接落到了数据库上。

解决

1.设置热点数据永不过期,有更新操作就更新缓存

2.加上互斥锁,只允许一个线程重建缓存, 其他线程等待重建缓存的线程执行完, 重新从缓存获取数据即可。

三、缓存雪崩

缓存雪崩是指在高并发下,大量的缓存key在同一时间失效,此时 Redis 跟没有一样,导致大量的请求落到数据库上。

比如:活动系统里面同时进行着非常多的活动,但是在某个时间点所有的活动缓存全部过期,此时大量请求全部落在了数据库上,高数量级别的请求直接打到数据库,数据库扛不住可能直接挂了。如果挂的是一个基础服务的库,那其他依赖他的库所有接口几乎都会报错,像雪崩一样,由缓存服务的问题,导致其他服务不可用,甚至整个系统不可用。

解决

1.批量往 Redis 存数据的时候,把每个 Key 的失效时间都加个随机值,这样可以保证数据不会再同一时间大面积失效

2.如果是集群部署,将热点数据均匀分布在不同的Redis 库中也能避免大面积失效

3.设置热点数据永不过期,有更新操作就更新缓存

4.依赖隔离组件,为后端限流、熔断并降级,比如Sentinel或Hystrix,并友好提示

四、缓存和数据库双写一致性

1.一致性

一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。主要分为强一致性、弱一致性、最终一致性

2.缓存模式

数据读取流程一般如下:

  • 如果读取的数据在缓存里有,那么就直接取缓存的

  • 如果缓存里没有,会去查询数据库

  • 将数据库查出来的数据更新到缓存中

  • 最后将数据返回给请求

如果仅仅读取数据的话,缓存的数据和数据库的数据是一致的。但是,当要写数据的时候,很可能造成数据库和缓存的数据不一致。

3.一致性问题现象

3.1.更新数据库后,更新缓存

image-20210821083853694

  • 线程1先更新数据库

  • 线程2再更新数据库,并更新缓存

  • 线程1由于网络等原因此刻才更新缓存

则缓存中保存的是线程1设置的旧数据,数据库保存的是线程2设置的新数据,导致数据不一致

3.2.更新数据库后,删除缓存

image-20210821085441158

  • 线程1更新数据库a=1,删除缓存

  • 线程3查询数据,缓存中没有,去查询数据库,此时查到的数据为a=1

  • 线程2更新数据库a=2,删除缓存,此时缓存为空

  • 线程3由于网络等原因此刻才更新缓存a=1

则缓存中的数据是a=1,数据库中的数据是a=2,也会导致数据不一致

3.3.删除缓存后,更新数据库

image-20210821100252108

  • 线程1 删除了缓存,还没有来得及写数据库

  • 线程2读取数据,发现缓存为空,则去数据库中读取数据并写入缓存,此时缓存中为旧数据

  • 线程1再写数据库

则缓存中的数据为旧数据,数据库中的数据为新数据,导致数据不一致

4.解决方案

1.设置Key的过期时间:能保证缓存和数据库的数据最终一致性

2.延时双删策略:写库前后都进行删除缓存操作(删缓存-写库-休眠一段时间-删缓存)

3.删除缓存重试机制:删除失败就多删除几次,保证删除缓存成功

3.定时更新缓存

4.通过加读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁

5.通过监听数据库的binlog日志及时的去修改缓存(阿里开源的canal)

5.无法保证强一致性

只能采取合适的策略来降低缓存和数据库间数据不一致的概率,保证弱一致性,最终一致性,而无法保证两者间的强一致性。缓存系统适用的场景就是非强一致性的场景,它属于CAP中的AP。追求绝对一致性的业务场景,不适合引入缓存,放入缓存的数据应该是对实时性、一致性要求不是很高的数据。

五、Redis清除策略

Redis对于过期键有三种清除策略:

1.被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key

2.主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key

3.当前已用内存超过maxmemory限定时,触发淘汰策略

六、Redis淘汰策略

在Redis 4.0 之前一共实现了 6 种内存淘汰策略,在 4.0 之后,又增加了 2 种策略,共8种:

volatile-ttl:从已设置过期时间的KV集中,优先对剩余时间短(time to live)的数据淘汰

volatile-random:从已设置过期时间的KV集中,随机选择数据淘汰

volatile-lru:从已设置过期时间的KV集中,优先对最近最少使用(Least Recently Used)的数据淘汰,即很久没被访问过的数据,以最近一次访问时间作为参考。

volatile-lfu:从已设置过期时间的KV集中,优先对访问频率最少,最不经常使用(Least Frequently Used)的数据淘汰(4.0加入),即最近一段时间被访问次数最少的数据,以次数作为参考。

allkeys-random:从所有KV集中,随机选择数据淘汰

allkeys-lru:从所有KV集中,优先对最近最少使用(Least Recently Used)的数据淘汰,即很久没被访问过的数据,以最近一次访问时间作为参考。

allkeys-lfu:从所有KV集中,优先对访问频率最少,最不经常使用(Least Frequently Used)的数据淘汰(4.0加入),即最近一段时间被访问次数最少的数据,以次数作为参考。

noeviction:不淘汰,若超过最大内存,返回错误信息

根据自身业务类型,配置好maxmemory-policy(默认是noeviction),推荐使用volatile-lru。

如果不设置最大内存,当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap),会让 Redis 的性能急剧下降。

当Redis运行在主从模式时,只有主节点才会执行过期删除策略,然后把删除操作”del key”同步到从节点删除数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值