缓存击穿
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来查询同一个key,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案
使用互斥锁(mutex key)
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}
缓存穿透
大量查询不存在的数据,从而跳过缓存直接查询数据,造成数据库崩溃。
由于不恰当的业务功能实现,或者外部恶意攻击不断地请求某些不存在的数据内存, 由于缓存中没有保存该数据,导致所有的请求都会落到数据库上,对数据库可能带 来一定的压力,甚至崩溃。
解决方案
- 针对缓存穿透的情况, 简单的对策就是将不存在的数据访问结果, 也存储到缓存中并设置过期时间,避免缓存访问的穿透。最终不存在商品数据的访问结果也缓存下来。有效的避 免缓存穿透的风险。
- 布隆过滤器:将数据库中所有的查询条件,放入布隆过滤器中,当一个查询请求过来时,先经过布隆过滤器进行查,如果判断请求查询值存在,则继续查;如果判断请求查询不存在,直接丢弃。
缓存雪崩
当缓存重启或者大量的缓存在某一时间段失效, 这样就导致大批流量直接访问数据 库, 对 DB 造成压力, 从而引起 DB 故障,系统崩溃。 举例来说,我们在准备一项抢购的促销运营活动,活动期间将带来大量的商品信息、 库存等相关信息的查询。为了避免商品数据库的压力,将商品数据放入缓存中存储。 不巧的是,抢购活动期间,大量的热门商品缓存同时失效过期了,导致很大的查询 流量落到了数据库之上。对于数据库来说造成很大的压力。
解决方案
1. 将商品根据品类热度分类, 购买比较多的类目商品缓存周期长一些, 购买 相对冷门的类目商品,缓存周期短一些;
2. 在设置商品具体的缓存生效时间的时候, 加上一个随机的区间因子, 比如 说 5~10 分钟之间来随意选择失效时间; (最优选择)
3. 提前预估 DB 能力, 如果缓存挂掉,数据库仍可以在一定程度上抗住流量 的压力 这三个策略能够有效的避免短时间内,大批量的缓存失效的问题。