缓存穿透、缓存击穿、缓存雪崩
在高并发场景下有一个常常被忽略的一个地方,读多还是写多,读多写少用缓存,写多读少用队列。
缓存穿透
用户请求的key在缓存中不存在,那么每次请求都会到达数据库,同时,数据库也没有查询到该数据,这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案
效验参数
可以根据自己key的规律,判断不合法的key请求并过滤掉。
布隆过滤器
布隆过滤器底层使用bit数组存储数据,该数组中的元素默认值是0。
布隆过滤器第一次初始化的时候,会把数据库中所有已存在的key,经过一些列的hash算法(比如:三次hash算法)计算,每个key都会计算出多个位置,然后把这些位置上的元素值设置成1。
原理:
如果多个位置中的元素值都是1,则说明该key在数据库中已存在。这时允许继续往后面操作。
如果有1个以上的位置上的元素值是0,则说明该key在数据库中不存在。这时可以拒绝该请求,而直接返回。
带来的问题
存在误判的情况。
存在数据更新问题。
如果布隆过滤器判断出某个key存在,可能出现误判。如果判断某个key不存在,则它在数据库中一定不存在。如果想减少误判率,可以适当增加hash函数。
缓存空值
当一个key请求,在缓存和数据库中都未找到对应数据时,可以把这个key缓存一个空值,这样在下次查询的时候直接返回空值,而无需再去查一次数据库。
带来的问题
要是攻击者随机key进行请求,我们缓存的空值不仅没有用处,还大量的消耗了缓存空间。
虽然可以给空值设置一个过期时间,但这种方案也是存在很大缺陷的。
缓存雪崩
因为缓存服务宕机或者热点缓存失效,所有请求都去查数据库,导致数据库连接不够或者数据库处理不过来,从而导致整个系统不可用。
解决方案
加锁
加锁排队,同一时刻只有一个请求才能访问某个key的数据库数据。
设置过期标志更新缓存
热点缓存失效可能的原因是缓存过期导致的,可以在缓存过期前自动续期来解决缓存过期问题,或者对于热点数据,直接设置缓存不过期。
交错失效时间
为了解决缓存雪崩问题,我们首先要尽量避免缓存同时失效的情况发生。
这就要求我们不要设置相同的过期时间。
可以在设置的过期时间基础上,再加个1~60秒的随机数。
这样即使在高并发的情况下,多个请求同时设置过期时间,由于有随机数的存在,也不会出现太多相同的过期key。
高可用
针对缓存服务器宕机的情况,在前期做系统设计时,可以做一些高可用架构。
比如:如果使用了redis,可以使用哨兵模式,或者集群模式,避免出现单节点故障导致整个redis服务不可用的情况。
扩展知识:redis哨兵模式
哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。
缓存击穿
缓存击穿实际上是缓存雪崩的一个特例,缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。击穿与雪崩的区别即在于击穿是对于某一特定的热点数据来说,而雪崩是全部数据。
解决方案
缓存设置不过期
对于热点数据,可以设置不过期的key来防止缓存击穿。