目录
缓存穿透
当客户端发处的请求的数据在redis和数据都不存在时,这样缓存永远都不会生效,这些请求都会打到数据库,对数据库造成危害
常见的解决方案有两种:
缓存空对象:在访问redis、数据库都不存在数据时,把空值存入redis(用于标志请求数据不存在),并设计TTL过期时间
这样就可以防止后续请求直击数据库
优点:实现简单,维护方便
缺点:
额外的内存消耗
可能造成短期的不一致
布隆过滤:设计一个布隆过滤器,在请求redis之前会通过布隆过滤器,布隆过滤器判断是否数据库中有这个数据,如果不存在则拒绝请求,存在则放行
优点:内存占用较少,没有多余key
缺点:
实现复杂
存在误判可能

这里需要介绍一下布隆过滤器
在数据库存储数据时,底层会把一个数据的唯一标识由很多哈希函数编制成很多二进制位存储在布隆过滤器中,由此布隆过滤器可以判断是否存在数据
注意:布隆过滤器判断存在时,也有可能是不存在数据,本质是不同 key (唯一标识)经过多个哈希函数后,恰好占用了同一组 bit 位导致的判断错误,但是概率很低
由此布隆过滤器可以大大减小数据库的负担
缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
给不同的Key的TTL添加随机值
利用Redis集群提高服务的可用性
给缓存业务添加降级限流策略
给业务添加多级缓存

缓存击穿
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见的解决方案有两种:
互斥锁
逻辑过期
逻辑分析:假设线程1在查询缓存之后,本来应该去查询数据库,然后把这个数据重新加载到缓存,但重建缓存数据时,进行的数据库多表查询可能比较复杂耗时较久,此时在写入缓存之前,有其他许多的线程都在这时访问缓存,都没有数据,都会在各自线程里去访问缓存最终到数据库,对数据库造成的压力较大

互斥锁解决办法:
因为锁能实现互斥性。假设线程过来,只能一个人一个人的来访问数据库,从而避免对于数据库访问压力过大,但这也会影响查询的性能,因为此时会让查询的性能从并行变成了串行,我们可以采用tryLock方法 + double check来解决这样的问题。
假设现在线程1过来访问,他查询缓存没有命中,但是此时他获得到了锁的资源,那么线程1就会一个人去执行逻辑,假设现在线程2过来,线程2在执行过程中,并没有获得到锁,那么线程2就可以进入休眠,若再次重试时线程1依旧没执行完就会再次重试,直到线程1把锁释放后,线程2获得到锁,然后再来执行逻辑,此时就能够从缓存中拿到数据了。

互斥锁方案: 由于保证了互斥性,所以数据一致,且实现简单,因为仅仅只需要加一把锁,所以没有额外的内存消耗,缺点在于有锁就有死锁问题的发生,且只能串行执行性能肯定受到影响
什么叫死锁?
举个例子,线程A持有锁A,想要继续执行,需要锁B
而线程B持有锁B,想要继续执行需要锁A
此时两个线程都互相持有对方需要的锁,但由于线程未完成不能释放锁,就陷入了死锁
逻辑过期解决办法:
我们之所以会出现这个缓存击穿问题,主要原因是在于我们对key设置了过期时间,假设我们不设置过期时间,其实就不会有缓存击穿的问题,但是不设置过期时间,这样数据不就一直占用我们内存了吗,我们可以采用逻辑过期方案。
逻辑过期方案:把过期时间作为值设计在value中,只需要进行逻辑比对就可以知道这个key是否过期,若线程1发现了逻辑过期,但是key和value不会消失 ,那么线程1获得互斥锁之后就会开启一个新的线程2,让线程2来重建缓存数据的同时线程1就返回过期的数据。若在线程2重构期间,其他线程来访问过期数据,因为线程2没有释放互斥锁,那么其他线程也会返回旧的数据,直到锁释放完毕
线程读取过程中不需要等待,性能好,有一个额外的线程持有锁去进行重构数据,但是在重构数据完成前,其他的线程只能返回之前的数据,有数据不一致性,且实现起来麻烦,
