六、Redis缓存穿透、缓存击穿、缓存雪崩的原因以及解决方案
引言
当有大量并发请求的时候,我们的数据库就会面临巨大的压力,甚至崩盘。这时候数据库心里OS:“有时候我真的顶不住这么多的请求,得赶紧雇个人来帮忙!”
这个时候,Redis小工闪亮登场~
那么这个时候我们就可以发现Redis的作用是帮数据库分担请求的压力,Redis这个小工就可以完成请求,而不需要去找后面的雇主。因为Redis手脚快(基于内存),所以Redis能出色、快速地完成请求。只有遇到Redis解决不了的问题,请求才会走向雇主。
但是,问题来了,有一天,雇主数据库发现请求都绕过Redis,直奔自己来了。Redis怎么了?
让我们采访一下Redis。
记者:“你为什么不好好工作呢?”
Redis:“我..我有难言之隐......”
记者:“有什么难言之隐,说出来,我们给你做主!”
Redis:“难言之隐有三,一、老板不给工钱;二、老板给的工资里面有张大面额假钞;三、老板给的工资里面有很多张假钞。”
这时候,我们就可以知道Redis不好好工作的原因了!
一、缓存不存在。(老板不给工钱)(缓存穿透)
二、缓存中有过期的热点key。(老板给的工资里面有张大面额假钞)(缓存击穿)
三、缓存中有大量过期的key。(老板给的工资里面有很多张假钞)(缓存雪崩)
然后有问题就要解决问题嘛~
开始解决
1. 缓存穿透
1.1. 原因
从上面我们得知,老板不给工钱,所以Redis心里不舒服,不能好好工作。但是重点来了,在缓存穿透这个场景里面,老板也是没有钱的,也就是请求的数据无论在Redis还是数据库中都是没有的。(tnnd,这tm不是操蛋嘛,没钱还雇我。Redis内心OS)
好了,现在我们知道缓存穿透是客户端请求的数据在Redis和数据库中都没有。如果数据库不存在用户A,但是黑客利用这个漏洞,频繁地去请求用户A的信息。那不就完蛋了嘛!
如图:
1.2. 解决方案
- 对空值缓存。当有请求频繁地请求空值,那么把空值也缓存到Redis,请求到Redis就返回。
- 黑白名单。设置白名单谁可以访问,或者谁有攻击趋势就拉进黑名单。
- 布隆过滤器。
2. 缓存击穿
2.1. 原因
老板给了张大面额的假钞,这听上去也没什么,但是如果面额是工资的一半,那换谁也会暴跳如雷。
同样的,在较短一段时间内,有大量请求去查询Redis中某个热点key,发现这个key是过时了的,那么请求就会到达数据库。注意,大量的请求同一时刻请求数据库,是有可能瞬间压垮数据库。
2.2. 解决方案
- 预设热门数据:在Redis高峰访问之前,把热点数据提前存入redis,增加热点数据的时长。
- 实时调整延长key的过期时间
- 使用锁。
(1)就是在缓存失效的时候(判断拿出来的值为空),不是立即去 load DB;
(2) 先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX)去 set 一个 mutex key;
(3)当操作返回成功时,再进行 load db 的操作,并回设缓存,最后删除 mutex key;
(4) 当操作返回失败,证明有线程在 load db,当前线程睡眠一段时间再重试整个 get 缓存的方法
3. 缓存雪崩
3.1. 原因
老板发的工资中大部分都是假钞。也就是Redis中存在大量过时的key,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大量并发的请求可能会瞬间把后端 DB 压垮。
注意:缓存击穿是某个热点key过期,缓存雪崩是大量key过期。
缓存雪崩会使Redis的命中率降低,导致数据库的压力激增,可能压垮服务器。
3.2. 解决方案
- 构建多级缓存。nginx 缓存 + redis 缓存 +其他缓存(ehcache 等),程序设计较为复杂。
- 使用锁或队列。用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。效率低,不适用高并发情况。
- 设置过期标志更新缓存。设置更新缓存的提前量。提前更新key的数据。
- 将缓存失效时间分散开。比如我们可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。