十二:redis击穿、穿透、缓存雪崩
概念区分: 击穿、穿透、雪崩
大量流量下:
缓存层 | DB | 场景 |
---|---|---|
存在 | 存在 | 正常(使用缓存) |
不存在 | 存在 | 缓存击穿 |
不存在 | 不存在 | 缓存穿透 |
- 击穿: 热key过期,导致大量流量打到db
- 穿透: 大量请求缓存、DB不存在的数据,一般是恶意攻击;
- 雪崩:大量的key过期,导致请求高峰,瞬间大量流量压到了DB;缓存层服务不在提供服务,所有请求流量压到了DB,导致DB无法对外正常提供服务,
进而波及使用这个DB的相关系统,最终导致整个系统崩溃;
击穿
热key过期,导致请求高峰,瞬间大量流量压到了DB;
我们可以看到,击穿是因为key过期,或者冷启动热点数据没有加载进缓存。
解决方案:
- 热点key过期时间错峰,平滑过期
- 缓存预热,提前将一些数据加载到缓存,而不是用户请求的时候缓存中没有查库在写入缓存。
- 定时刷新值,在key过期前已经重新更新了键的值以及最新的过期时间
穿透
大量请求缓存、DB不存在的数据,一般是恶意攻击
从上面我们可以看到,正常用户的操作一般不可能产生穿透的场,极有可能是恶意攻击,
解决方案:
- 对不存在的key缓存默认值,防止同一key的大量请求被压到DB
- 对不存在的key加入到布隆过滤器,再次查询这个key的时候,
缓存不存在判断是否在布隆过滤器中,在的话直接返回空值 - 对上面穿透请求的ip进行名单记录,进行限制
1. 默认空值
class Test {
public String getValue(String key) {
//1.缓存查key是否存在
String value = redisService.get(key);
if (StringUtils.isEmpty(value)) {
//2. 查询db
value = dbService.get(key);
if (StringUtils.isEmpty(value)) {
//3. 缓存默认值
redisService.set(key, "1");
return "1";
}
}
return value;
}
}
2. 布隆
class Test {
public String getValue(String key) {
//1.缓存查key是否存在
String value = redisService.get(key);
if (StringUtils.isEmpty(value)) {
//2. 缓存不存在,是否键在布隆中
if(bollfilterContain(key)) {
return "1";
}
//3.查db
value = dbService.get(key);
}
return value;
}
}
上面的1,2结果方案还有漏洞,我们上面的代码只能抵挡同一key的大量请求,
如果攻击方每次都构造了不同的key来进行攻击呢?
这个时候,首先接口要有鉴权,根据攻击的手法一般的发起者会在他的几台几十台机器上进行自动化请求那么我们可以对同一ip进行限流;
如果攻击继续升级,攻击方调动了大量肉鸡,ip封不过来,怎么怎么办呢?
对这里的服务进行降级,部分可用。
雪崩
当大量的key同一时间过期,导致所有的请求都落到了数据库上;
单机redis挂掉,导致所有请求都落到了数据库上。
解决方案:
- redis使用集群(主从、sentinel哨兵)避免单机故障
- 设置热点key永不失效
- 设置key错峰过期,平滑过期
- 三级缓存使用缓存在应用或者memcache(用户请求——->redis—>应用hashmap\memcache---->db)
- 降级服务,保证服务不挂掉