提到Redis,相信大多数开发者在面试或者实际开发过程中对缓存雪崩,穿透,击穿并不陌生吧;下面就来看看这三者到底有什么区别?又如何防止这种情况发生呢?
先来看看什么是缓存雪崩
开发中,针对系统中的热点数据都会去做缓存 ,一般缓存都是定时任务去刷新,或者是查不到之后去更新的,定时任务刷新就有一个问题:
举个简单的例子:如果所有热点数据的Key失效时间都是12小时,中午12点刷新的,零点的时候(距离中午刷新点刚好过了12个小时)因某种原因(活动,促销等)有大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住。此时,如果没用什么特别的方案来处理这个故障时,就会非常难堪了,因为就算你重启了数据库,但是数据库立马又被新的流量给搞挂了。
同一时间大面积失效,那一瞬间Redis跟没有一样,那这个数量级别的请求直接打到数据库几乎是灾难性的,你想想如果打挂的是一个用户服务的库,那其他依赖他的库所有的接口几乎都会报错,如果没做熔断等策略基本上就是瞬间挂一片的节奏,你怎么重启用户都会把你打挂.此时用户内心
如何防止这种情况呢?
处理这个问题的关键:就是要确保数据不会在同一时间大面积失效
思路有了,处理起来就不难了,下面是几种方案:
- 在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效;
- 如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题;
- 设置热点数据永远不过期,有更新操作就更新缓存就好了
什么是缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。
像这种如果不对参数做校验,数据库id都是大于0的,我一直用小于0的参数去请求你,每次都能绕开Redis直接打到数据库,数据库也查不到,每次都这样,并发高点就容易崩掉了。
如何防止缓存穿透
在接口层增加校验,比如 对参数做校验,不合法的参数直接Return;如上面提到的非法的id(id<=0),对其进行直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将对应Key的Value对写为null、稍后重试等等这样的值具体取什么看业务场景,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用
)。这样可以防止攻击用户反复用同一个id暴力攻击,但是我们要知道正常用户是不会在单秒内发起这么多次请求的,在网关层Nginx也有相关的配置项,可以对单个IP每秒访问次数超出阈值的IP都拉黑。
此外,Redis中还有一个高级用法布隆过滤器(Bloom Filter)这个也能很好的防止缓存穿透的发生,他的原理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在就直接return,存在的话就去查了数据库刷新缓存中的Key,Value再return。
什么是缓存击穿
缓存击穿跟缓存雪崩有点像,但是又有点不一样,缓存雪崩是因为大面积的缓存失效,打崩了数据库,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库. 就好像在一个完整的东西上钻了一个洞.
如何防止缓存击穿?
了解为什么会产生缓存击穿后,怎么防止就变得很简单了
设置热点数据永远不过期。