缓存穿透
什么是缓存穿透?
假如我们有一个学生表一共有10条数据,对应的id为1-10。我们有一个请求是通过id去查询学生的信息。正常的流程是请求先到redis里面去找,如果命中就将查询到的结果反回,如果没有就去mysql数据库中找,并将结果返回,同时存到redis当中。但是如果我们请求查询一个数据库中不存在的id,那么每次mysql数据库查询不到数据,也不会存入redis中,就会导致每次查询这样不存在的数据都会直接查找数据库。黑客可以运用这一点,发送大量不存在数据的请求,来对我们进行攻击。
解决缓存穿透的方法
- 方案1:缓存空数据,及时查询结果为null,也将这个结果存入redis
- 优点:操作简单
- 缺点:消耗内存,如果mysql数据库中新插入了所对应的数据,但是redis中存储的可能仍是null,会导致不一致问题。
- 方案2:布隆过滤器
在缓存预热阶段,预热布隆过滤器,在请求去redis中查询之前,现在布隆过滤器中进行查询所要查询的id是否存在于布隆过滤器中,如果不存在直接返回,如果存在再去redis中进行查询。(布隆过滤器的实现依赖于bitmap,是一个以bit为单位的数组,只能存储0/1,比如我们把id=1,存储到布隆存储器中,需要经过3个hash函数的计算,让某些下标为1,那么查询某个id是否存在,就是将传过来的id用相同的函数计算,看看布隆过滤器中对应的位置是否都为1,但是存在误判率。比如经过函数三个函数计算,bitmap中下标 1 ,5 ,8 ,9 ,11 ,15被标记为1,这时传过来一个不存在的id,但是这个id通过三个哈希函数的计算,认为如果5,8,15,这三个下标位置为1就视为这个id存在,这样就造成了误判)
可通过Redisson或Guava去实现布隆过滤器。
- 优点:内存占用较少
- 缺点:实现复杂,可能存在误判。
缓存击穿
什么是缓存击穿
在redis中给某个key设置了过期时间,但是当redis过期的时候恰好有对这个key的大量请求发了过来,这些并发的请求可能会瞬间把数据库压垮。
在redis查询不到数据,会去数据库中查询然后再次在redis中记录,那么为什么还会把数据库压垮呢?
因为所要查询的数据可能是要联合很多张表去查询,就会消耗较长的时间,无法去快速写入到redis。
如何解决缓存击穿
方案1:互斥锁
在第一个访问的线程后发现缓存未命中,让他获取到锁,接着去查询数据库并重建缓存,在释放锁之后其他线程才能在缓存中获取数据。如果在线程1释放锁之前,有其他线程来查询该数据,就会获取互斥锁失败,休眠一会后再重试查询该数据。
特点
- 强一致(每个线程查询到的数据保证都是一样的)
- 性能差(在第一个获取到锁的线程释放锁之前,其他线程要休眠和重试查询,浪费性能)
方案2:逻辑过期
在redis中并不设置过期时间,而是在数据库中加入一个逻辑过期时间的字段。在查询缓存中发现逻辑过期时间已经过期,那么他就会获取一个互斥锁,并创建一个新的线程2,由这个线程去查询数据库并重建缓存数据,写入缓存后重置逻辑过期时间,然后释放锁。而线程1并不等待线程二去完成这些流程,在获取到互斥锁之后,线程1直接返回过期的数据。如果在线程二释放锁之前,又有其他线程去访问这条数据,那么也会返回过期数据。而线程2释放锁之后再有=线程访问该条数据,就会缓存命中,返回没有过期的数据。
特点
- 高可用,性能优(不用像方法一那样让其他线程一直等待,而是直接返回过期的数据)
- 但是不能保持数据的一致性(过期的数据可能和没有过期的数据不一致)
缓存雪崩
什么是缓存雪崩
缓存雪崩是指在同一时段大量的key失效/或者Redis服务器宕机,导致大量请求到达服务器,带来巨大的压力。
解决方案
- .给不同的Key的TTL添加随机值
- 利用Redis集群提高服务的可用性
- 给缓存业务增加降级限流策略(降级可作为系统的保底策略,适用于穿透、击穿、雪崩)
- 给业务添加多级缓存