缓存穿透(查不到)
概念
用户想要查询一个数据,发现Redis数据库中没有,也就是缓存没有命中,于是向持久层数据库(例如Mysql)查询,发现也没有,于是本次查询失败。当用户很多时,缓存都没有命中(秒杀),于是都去请求持久层数据库,这会给持久层数据库造成很大压力,这时候就相当于出现了缓存穿透。
解决方案
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。
- 特点
特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。
- 结构
布隆过滤器是一个 bit 向量或者说 bit 数组,长这样:
如果我们要映射一个值到布隆过滤器中,我们需要使用多个不同的哈希函数生成多个哈希值,并对每个生成的哈希值指向的 bit 位置 1,例如针对值 “baidu” 和三个不同的哈希函数分别生成了哈希值 1、4、7,则上图转变为:
我们现在再存一个值 “tencent”,如果哈希函数返回 3、4、8 的话,图继续变为:
值得注意的是,4 这个 bit 位由于两个值的哈希函数都返回了这个 bit 位,因此它被覆盖了。现在我们如果想查询 “dianping” 这个值是否存在,哈希函数返回了 1、5、8三个值,结果我们发现 5 这个 bit 位上的值为 0,说明没有任何一个值映射到这个 bit 位上,因此我们可以很确定地说 “dianping” 这个值不存在。而当我们需要查询 “baidu” 这个值是否存在的话,那么哈希函数必然会返回 1、4、7,然后我们检查发现这三个 bit 位上的值均为 1,那么我们可以说 “baidu” 存在了么?答案是不可以,只能是 “baidu” 这个值可能存在。
这是为什么呢?答案跟简单,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值 “taobao” 即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断 “taobao” 这个值存在。
- 如何选择哈希函数和布隆过滤器的长度
很显然,过小的布隆过滤器很快所有的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。
另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。
缓存空对象
当存储层布命中后,即使返回空对象也将其存储起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。
但是这种方法有两个问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多空值的键;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层数据有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿(查询量太大,缓存过期)
概念
这里需要注意和缓存穿透的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开一个洞。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同市访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大。
解决方案
设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。
加互斥锁
分布式锁:使用分布式锁,保证每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩
概念
缓存雪崩,指在某一时间段,缓存集中过期失效,Redis宕机。产生雪崩的原因:比如双十一零点会迎来一波抢购,这波商品比较集中的放入了缓存,假设缓存一小时。那么到凌晨一点,这批商品的缓存都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会到达存储层,造成存储层挂掉。
其实集中过期到不是非常致命,比较致命的是缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对服务器造成的压力是不可预知的美很有可能瞬间就把数据库压垮。
双十一:停掉一些服务(保证主要的服务可用),例如退款功能。
解决方案
Redis高可用
既然Redis可能挂掉,那就多增设几台redis,这样一台挂掉之后其他的还能继续工作,其实就是搭建集群。(异地多活)。
限流降级
在缓存失效后,通过加锁或者队列来控制读数据写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
在正式部署前,把可能的数据预先访问一遍,这样大部分的数据就回家再到缓存中。在即将发生大并发访问前手动出发家在缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
- Post title:缓存穿透与雪崩
- Post author:洪笳淏
- Create time:2021-11-12 15:27:00
- Post link:https://jiahaohong1997.github.io/2021/11/12/缓存穿透与雪崩/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.