缓存的另外三个问题(穿透,雪崩,击穿)
一、缓存穿透
1、产生原因
客户端可能会恶意请求一些缓存中和数据库中都不存在的数据,这样缓存永远不会生效,请求都会打到数据库当中,给数据库造成压力。
2、解决方案
a、缓存空对象
思路:对于不存在的数据也可以在缓存中建立缓存,将值设为空,并设置一个较短的TTL时间。
有点:实现简单,维护方便。
缺点:增加额外的内存消耗,短期数据不一致。
可参考一下代码:
public Shop queryWithMutex(Long id){
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
if (StrUtil.isNotBlank(shopJson)){
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return shop;
}
if (shopJson != null){
return null;
}
//实现缓存重建
String lockKey = "lock:shop"+id;
Shop shop = null;
try {
boolean isLock = tryLock(lockKey);
//判断是否获取成功
if (!isLock){
Thread.sleep(50);
return queryWithMutex(id);
}
//如果为空则查询数据库中是否纯在
shop = getById(id);
//由于查询数据库比较快,模拟延时
Thread.sleep(200);
if (shop == null){
//店铺不存在
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"", 2, TimeUnit.MINUTES);
return null;
}
//缓存中不存在,存入缓存
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//解除锁
unLock(lockKey);
}
return shop;
}
b、布隆过滤
思路:利用布隆过滤算法,在请求进入redis之前判断是否存在秒如果不存在则直接拒绝请求,
优点:内存占用少。
缺点:实现起来比较复杂,存在误判的可能性。
c、其他
做好数据的基础格式校验:比如让id按照一定规格设定,让恶意用户不容易猜到。
加强用户权限校验。
做好热点参数限流。
二、缓存雪崩
1、产生原因
在同一段时间大量缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
2、解决方案
给不同的key的TTL添加随机值,避免批量导入后有一段时间会批量失效。
热龙Redis集群提高服务的可用性。
给缓存添加降级限流策略。
给业务添加多级缓存。
三、*缓存击穿
1、产生原因
热点key在某一时间段呗高并发访问,缓存重建的耗时较长。
热点key突然过期,因为重建时间过长,这段时间内大量请求落到数据库上,带来巨大冲击
2、解决方案
a、互斥锁
思路:给线程重建过程加锁,确保重建过程只有一个线程执行,其他线程等待。
优点:实现简单,没有额外消耗内存,一致性好。
缺点:等待导致性能下降,有死锁风险。
b、逻辑过期
思路:热点key缓存永不过期,而是设置一个逻辑过期时间,查询到数据时通过对逻辑过期时间的判断来决定是否需要重建缓存。重建缓存也通过互斥锁保证单线程执行。其他线程无需等待,直接查询旧的数据即可。
优点:线程无需等待性能较好。
缺点:不保证一致性,有额外内存消耗,实现复杂。
Q.E.D.