缓存穿透

key对应的数据在底层数据库不存在,每次针对key的请求都在redis中获取不到对应的缓存,请求都会直接压到递增数据库,从而压垮底层数据库。

场景

服务遭受恶意攻击,黑客通过不正常的请求频繁访问服务器

解决方案

1.对空值进行缓存):如果一个查询返回的数据为空(不管是数据是否存在),我们让然把这个空结果进行缓存,对空结果设置过期时间,一般不超过5分钟。
2.设置白名单:使用biymaps类型定义一个可访问的白名单,名单id作为bitmaps的偏移量,每次访问和bitmap里的id进行比较,如果id不存在bitmaps里面,进行拦截,不允许访问
3.采用布隆过滤器:它实际上是一个很长的二进制向量(位图)和一些列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
将所有存在的数据hash放如一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免对底层系统的查询压力
4.进行实时监控:当发现redis的命中率开始急剧降低,需要排查访问对象和访问数据,和运维人员配合,设置黑白名单限制服务。

缓存击穿

key对应的数据存在,但是在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从递增数据库获取数据然后设置到缓存中,这个时候大并发的请求可能把递增数据库压垮。

场景

高并发,如618双11

解决方案

1.预先设置热门数据:在redis高峰访问之前,把一些热门的数据提前存入到redis中,加大这个热门数据key的过期时间
2.实时调整:现场监控那些数据热门,实时调整key的过期时间
3.使用锁(效率慢):
1)在缓存失效的时候,不去立即load db.
2)先使用缓存工具的某些带成功操作返回值的操作(比如redis的setnx)去设置一个mutex key
3)当操作返回成功时,在进行load db的操作,并回设缓存,最后删除mutex key
4)当操作返回失败时,证明有线程正在load db,当线程睡眠一段时间再重试get缓存方法
image.png

缓存雪崩

在一段时间内,出现大量key集中过期。

解决方案

1.构建多级缓存:nginx缓存+redis缓存+其他缓存
2.使用锁或队列:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层的存储系统上,不适用高并发情况。
3.设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台更新实际key的缓存
4.将缓存失效时间分散:
比如我们在原有的失效时间基础上增加一个随机值,比如1~5分钟随机,这样每一个缓存的过期时间重复率会降低,就很那引发集体失效事件

分布式锁

随着业务发展的需要,原单体单机部署的系统被演化为分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同的机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的javaAPI并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁需要解决的问题。

实现方案

1.基于数据库实现分布式锁
2.基于缓存(redis等)
3.基于zookeeper
每一种分布式解决方案都有各自的优缺点:
1.性能(redis最高)
2.可靠性(zookeeper最高)
这里,我们就基于redis实现分布式锁

解决方案

1.在redis中使用setnx上锁,使用del解除锁。
2.如避免锁一直没释放可以通过expire设置key的过期时间,让其自动释放(为避免上锁后异常无法设置时间可使用 set key value nx ex 10)。但是同时会引出释放锁错误的问题,场景如下:
1)A、B、C三个同时进行抢锁操作。
2)A抢到锁进行操作,突然服务器卡顿,导致锁自动超时
3)A锁超时后,B抢到锁开始进行操作
4)A服务恢复进行锁释放,由于锁为进行