# 雪崩
雪崩就是指缓存中大批量热点数据过期后系统涌入大量查询请求,因为大部分数据在
Redis
层已经失效,请求渗透到DB
,引起数据库压力造成查询堵塞甚至宕机
。
举个简单的例子
一个电商平台,如果首页所有的 Key 失效时间都是 12 小时,零点有个限时秒杀活动大量用户杀来,假设当时 6000/s 个请求,本来缓存可以顶住 5000/s 个请求,但是当时所有缓存的 Key 都失效了,这时每秒 6000 个请求全部落在数据库,犹如洪水决堤,造成宕机。
解决方案
- 不设置过期时间 (不建议)
- 设置不同的过期时间,避免同一时间大量 key 失效。比如利用随机模块生成一个过期时间,这样可以保证数据不会在同一时间大面积失效
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同 Redis 和数据库中,有效分担压力
# 击穿
缓存击穿跟缓存雪崩有些类似,雪崩是大面积缓存失效,导致数据库崩溃,而缓存击穿是一个
Key
是热点,不停地扛住大并发请求,全都集中访问此Key
, 而当此 Key 过期瞬间,持续的大并发就击穿缓存,全都打在DB
上,就像在完好无损的杯子上凿开一个洞。
解决方案
- 把这个热点 key 设置为永久有效
- 使用互斥锁,这是比较常用的方法,简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去查询数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX 或者 Memcache 的 ADD)去 set 一个 mutex key,当操作返回成功时,再进行查询数据库的操作并回设缓存
# 穿透
指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果 key 不存在或者 key 已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。
举个简单的例子
比如我传一个用户 id 为 - 1,这个用户 id 在缓存里面是肯定不存在的,所以会去数据库里面查询,如果有搞事情的人,大批量请求并传用户 id 为 - 1,那 redis 就形同虚设,无用武之地,导致数据库压力过大而崩溃。
解决方案
-
在接口层增加校验,比如用户鉴权校验,不合法的参数直接返回。不相信任务调用方,根据自己提供的 API 接口规范来,作为被调用方,要考虑可能任何的参数传值。
-
布隆过滤器(Bloom Filter), 能很好地防止缓存穿透。原理就是利用高效的数据结构和算法快速判断出你这个 Key 是否在
DB
中存在,从而避免了对底层存储系统的查询压力。 -
在缓存查不到,
DB
中也没有的情况,可以将对应的key
的 value 写为 null,或者其他特殊值写入缓存,同时将过期失效时间设置短一点,以免影响正常情况。这样是可以防止反复用同一个 ID 来暴力攻击
# 总结
一般避免以上情况发生从三个时间段去分析:
- 事前:
Redis高可用
,主从
+哨兵
,Redis cluster
,避免全盘崩溃 - 事中:本地
ehcache缓存
+Hystrix限流
+降级
,避免 DB 被打死 - 事后:Redis 持久化
RDB
+AOF
,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据