1.缓存穿透、缓存击穿、缓存雪崩解决方案
缓存穿透
查询一个不存在的数据,100万条请求去查,结果缓存里面没有,都走了数据库。
解决方案:
1.缓存一个null的结果在缓存
2.布隆过滤器
缓存击穿
某一个热点数据,当它缓存失效的一瞬间,进来了大量请求,此时这些请求都去走了数据库,导致数据库压力过大崩溃了。
解决方案:
1.热点key设置永不过期(不推荐)。
2.加互斥锁,加锁保证只有拿到锁的请求,能够执行查询数据库,其他未拿到锁的请求进入阻塞等待状态。
缓存雪崩
当缓存大面积同时失效,这时候大量请求都走了数据库。缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。
解决方案:
设置缓存的时候过期时间不要用同一个,在一定时间的基础上加一个随机数,保证不会在同一时间大量失效。
2.如何保证缓存和数据库的一致性
1)双写
当修改数据的时候,同时更新数据库以及缓存的数据,并且一定要是在同一把锁中。
存在问题:
1.双写过程中发生异常,就有可能发生数据库更新了,缓存未更新;或者缓存更新了,数据库没更新。
2.并发问题,如两个并发请求A、B这种情况。A更新数据库,B更新数据库,B更新缓存,A更新缓存,这种情况就会导致B更新的缓存内容丢失了。所以说双写的过程需要加锁。
2)更新后删除缓存(比双写要好一点)
每当修改了数据时,把该数据在缓存里面的也删除了。这样下次请求来的时候,会去数据库查一次最新的数据,然后重新放入缓存中。
延迟双删:更新了数据库以后,往消息队列里面添加一条消息,等待一定时间后删除缓存。
总结下来:两个方法其实都不能保证强一致性。如果真要保证的话只能够牺牲性能来解决,加悲观锁等等。大多数情况下使用延迟双删策略。
3.分布式锁
在分布式系统下,本地锁满足不了分布式系统的场景,本地锁只适用单体项目,由此引出了分布式锁。
原理:setnx 写入一个key如果不存在就写入,存在返回null,利用redis的这个命令,实现占坑的目的。高并发场景下,大量的请求进来,只有拿到锁的请求才能够执行后续代码,执行完毕后,需要释放锁。没拿到的可以一直自旋等待,直到获取到锁。比如我们的抢票啊,约课等场景。
流程:加锁保证原子性,解锁保证原子性