缓存穿透、缓存击穿、缓存雪崩
缓存穿透
说明:缓存穿透是指客户端请求缓存和数据库都不存在的数据,这样缓存永远不会生效,这些请求都会打到数据库上。
如图:每次请求都经过3、4、5,每次都要操作数据库,也就是缓存无效了。如果是1w 请求,1w 请求最终都打到数据库上。
解决方案
常见的解决方案有两种:缓存空对象,使用布隆过滤器;
- 缓存空对象
客户端请求缓存和数据库都不存在的数据,返回时将控制设置到redis中,并且设置过期时间。
public Product getProductCache(String productId) {
// 从缓存中获取数据
String key = (String)redisTemplate.opsForValue().get("productId");
if (key == null) {
// 从数据库获取查询
Product product = ProductService.selectByProductId(productId);
if (Objects.nonNull(product)) {
// 数据库存在数据,则重新设置缓存
redisTemplate.opsForValue().set(productId, JSON.toJSONString(product), 3000L);
return product;
}else{
// 从缓存和数据库都是空,那么把空值放进缓存,设置过期时间,防止缓存穿透
redisTemplate.opsForValue().set(productId, null, 2000L);
return null;
}
}
return JSON.parseObject(key, Product.class);
}
- 使用布隆过滤器
布隆过滤器本次不做说明。
缓存击穿
说明:缓存击穿是针对热点 key 而言,某个瞬间大量请求该 key,且 key 不存在,这些请求全部打到数据库上。
如图:热键瞬间失效,大量的请求都打到了 mysql 上,mysql 可能承受不住如此如此之高的请求量。
解决方案
常见的解决方案有两种:加互斥锁,热点数据不过期;
- 加互斥锁
在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。
- 热点数据不过期
直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存,或者在 key 对应的 value 内容发生变化主动去更新缓存。
缓存雪崩
说明:大量的热点 key 设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库被打挂。
如图:key1~key8 的过去时间相同,同时失效,对于 key1~key8 的所有查询请求都打到了 mysql 上。
解决方案
常见的解决方案有两种:打散 key 过期时间,热点数据不过期,加互斥锁;
- 打散 key 过期时间
既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。
- 加互斥锁
在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。
- 热点数据不过期
直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存,或者在 key 对应的 value 内容发生变化主动去更新缓存。