目录
缓存穿透
解决方案一:缓存空数据
解决方案二:布隆过滤器
介绍:布隆过滤器
bitmap(位图)
布隆过滤器作用
执行流程
如何查询数据?
误判率:
总结
面试回答
缓存击穿
解决方案一:互斥锁
解决方案二:逻辑过期
面试回答
缓存雪崩
解决缓存雪崩的方法
面试回答
缓存穿透是指在使用缓存系统时,某个查询请求无法从缓存中获取到数据,因此必须去数据库中获取数据,但是数据库中也不存在此查询请求所对应的数据,导致每次查询都返回无数据,造成了无谓的数据库访问请求和资源浪费。
为了避免缓存穿透的问题,可以采用一些方法,例如在缓存中缓存空值,或使用布隆过滤器,在缓存查询时判断查询请求是否合法等。
例:
一个get请求:api/news/getById/1
缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存
缓存一个空对象,然后给这个空对象的缓存设置一个过期时间,这样下次再查询该数据的时候,就可以直接从缓存中拿到,从而达到了减小数据库压力的目的。
优点:简单
缺点:
1)需要缓存层提供更多的内存空间来缓存这些空对象,当这种空对象很多的时候,就会浪费更多的内存;
2)会导致缓存层和存储层的数据不一致,即使在缓存空对象时给它设置了一个很短的过期时间,那也会导致这一段时间内的数据不一致问题。
注意:缓存的数据空对象在一定时间后也必须从缓存中清除,以确保缓存数据的正确性和一致性。
它可以预先将数据的一部分哈希值映射到一个二进制向量中,缓存查询时先对请求进行哈希操作,并在二进制向量中定位是否存在。如果不存在,则说明数据库中也一定不存在此查询请求的数据,从而可以避免无谓的数据库查询。
优点:内存占用较少,没有多余key
缺点:实现复杂,存在误判
相当于是一个以(bit)位为单位的数组,数组中每个单元只能存储二进制数0或1
布隆过滤器可以用于检索一个元素是否在一个集合中。
存储数据:id为1的数据,通过多个hash函数获取hash值,根据hash计算数组对应位置改为1
查询数据:使用相同hash函数获取hash值,判断对应位置是否都为1
举个例子,假设要将字符串"hello world"添加到布隆过滤器中,该布隆过滤器使用3个哈希函数,位数组大小为10,步骤如下:
创建一个大小为10的位数组,初始化所有位为0。
设定3个哈希函数,如下:
哈希函数1:将字符串转化为一个整数,然后将整数对10取余
哈希函数2:将字符串中的每个字符的ASCII码值相加,然后将和对10取余
哈希函数3:将字符串翻转,然后将翻转后的字符串转化为一个整数,然后将整数对10取余
对字符串"hello world"进行3次哈希操作,得到3个哈希值分别为2、5、8。
将位数组中2、5、8这3个位置的值都设为1。
添加完数据后,当检查某个元素是否在布隆过滤器中时,只需要进行和添加数据相同的哈希函数操作,检查对应的位数组的值是否为1即可。如果所有哈希函数操作对应的位数组值都为1,那么该元素可能在集合中,如果其中任何一个位数组值为0,则该元素一定不在集合中。
布隆过滤器的数据查询过程主要分为以下几个步骤:
1.计算hash函数:对要查询的元素进行k次哈希操作,得到k个哈希值。
2.判断是否为1:查位数组中这k个位置的值是否都为1。
3.如果这k个位置的值都为1,则认为该元素可能在集合中;否则,认为该元素一定不在集合中。
举个例子,假设布隆过滤器中已经添加了字符串"hello world",布隆过滤器使用3个哈希函数,位数组大小为10,查询字符串"hello"是否在布隆过滤器中,步骤如下:
1.对字符串"hello"进行3次哈希操作,得到3个哈希值分别为2、5、8。
2.检查位数组中2、5、8这3个位置的值是否都为1。
3.如果这3个位置的值都为1,则认为字符串"hello"可能在集合中。
由于布隆过滤器可能会出现误判,因此在实际应用中,需要根据具体的应用场景来确定误判率的可接受范围,并相应地设置哈希函数数量和位数组大小。同时,需要注意,布隆过滤器无法删除已添加的数据。
误判率是布隆过滤器中一个重要的指标,也称为假阳性率。它表示在数据是否存在于缓存中的判断中,实际数据不存在于缓存中,但是布隆过滤器将其误判为存在的概率,即将非目标元素误认为目标元素的概率。误判率通常是在布隆过滤器序列初始化时设置和确定的,一般是通过调整哈希函数、二进制向量大小等参数来控制误判率。
如果误判率过高,布隆过滤器会将大量的查询请求误判为目标数据存在于缓存中,导致缓存误命中,进而增加系统开销,并且降低了系统缓存命中率。因此,降低误判率是布隆过滤器设计中需要考虑的一个重要因素。但是,误判率过低也会导致大量的查询请求绕过了缓存查询,从而浪费了缓存的作用。
布隆过滤器由位图和n个hash函数构成。布隆过滤器的操作是一个key经过多个hash函数,然后对位图大小进行取余等到多个槽位并对应置为1。判断时只要有一个槽位为0就一定不存在该key。
布隆过滤器能确定一个key一定不存在,不能判断key一定存在,但可控假阳率确定存在。
布隆过滤器不支持删除操作,可以通过两个布隆过滤器解决(依然存在假阳率,但会低一些),添加放在第一个布隆过滤器,删除放在第二个布隆过滤器。即要判断key是否存在,首先检查第二个布隆过滤器是否删除过,如果删除过就往第一个布隆过滤器插入。
布隆过滤器根据n和p算出m和k,hash函数个数是利用开放寻址法来计算的。
面试官:什么是缓存穿透 ? 怎么解决 ?
候选人: 嗯~~,我想一下 缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写 入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。 解决方案的话,我们通常都会用布隆过滤器来解决它
面试官:好的,你能介绍一下布隆过滤器吗?
候选人: 嗯,是这样~ 布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是 redisson实现的布隆过滤器。 它的底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一 开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据 的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一 个key的存在。查找的过程也是一样的。 当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置 这个误判率,大概不会超过5%,其实这个误判是必然存在的,要不就得增 加数组的长度,其实已经算是很划分了,5%以内的误判率一般的项目也能 接受,不至于高并发下压倒数据库。
给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能导致数据库压力过大,甚至导致宕机。
使用互斥锁或分布式锁可以保证在访问缓存时,任何一个请求只能访问到缓存中的一个唯一的数据,一旦缓存中不存在该数据,则只有一个请求能够访问到数据库,其他请求需要等待缓存写入操作完成后再次查询缓存。
当一个热点 key 过期的时候,首先将这个 key 的过期时间设置成一个很小的值(或者设置为永不过期,但是有一个过期时间的字段),同时开启异步线程去更新此 key 的缓存值,使其尽快更新到缓存中。在更新缓存值的过程中,可以使用互斥锁或分布式锁等方式确保多个异步线程不会重复更新同一个 key。
当缓存值更新完成之后,再将这个 key 的过期时间重新设置成通常的过期时间,这样下一次的请求就会从缓存中获取到新的数据,而不会直接访问数据库。
面试官:什么是缓存击穿 ? 怎么解决 ?
候选人:缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过 期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能 会瞬间把 DB 压垮。
解决方案有两种方式:
第一可以使用互斥锁:当缓存失效时,不立即去load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db的操作并回设缓 存,否则重试get缓存的方法
第二种方案可以设置当前key逻辑过期,大概是思路如下: ①:在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前 key设置过期时间 ②:当查询的时候,从redis取出数据后判断时间是否过期 ③:如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据, 这个数据不是最新
当然两种方案各有利弊: 如果选择数据的强一致性,建议使用分布式锁的方案,性能上可能没那么 高,锁需要等,也有可能产生死锁的问题 如果选择key的逻辑删除,则优先考虑的高可用性,性能比较高,但是数据 同步这块做不到强一致。
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
使用分布式缓存(集群)
使用分布式缓存,将缓存数据分散到多个节点服务器上,当某一节点的缓存数据失效时,可以自动转向其他节点上的缓存数据,从而避免单点失效导致的缓存雪崩。
设置不同的过期时间(给不同的Key的TTL添加随机值)
将不同的缓存数据的过期时间设置为不同的时间,避免在同一时刻所有的缓存数据都失效的情况发生,从而分散请求的访问压力。
限流(给缓存业务添加降级限流策略)
对请求进行限流,并且控制同一时刻发起的请求数量,以避免大量的请求同时访问数据库。
热点数据永不过期
将一些重要的热点数据设置为永不过期,或者定期更新热点数据,以保证它们一直存在于缓存中,并且可以被及时访问。
通过上述方法的组合使用,可以有效地避免缓存雪崩的问题,并确保系统缓存的性能和稳定性。
面试官:什么是缓存雪崩 ? 怎么解决 ?
候选人:缓存雪崩意思是设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB 瞬时压力过重雪崩。与缓存击穿的区别: 雪崩是很多key,击穿是某一个key缓存。
解决方案主要是可以将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重 复率就会降低,就很难引发集体失效的事件。