【redis】缓存穿透、缓存击穿、缓存雪崩区别
缓存穿透、缓存击穿、缓存雪崩区别
这三个名字有点像, 尤其是缓存击穿和缓存穿透, 很多同学容易记混忘记, 而且面试经常被问到, 每次都要去看一遍, 非常难受, 本文将以大白话的形式解释这三个名词
1. 缓存穿透
穿透的意思: 一穿而过, 穿的是谁? 那肯定是redis, 穿过redis会发生什么? 回答这个问题之前我们要知道, redis作为缓存是用来干什么的. 当然是给数据库做缓存, 其除了提高性能的同时能避免数据库承受过多的压力, 那么很明显, 穿过redis后请求会到达数据库, 那么这叫缓存穿透吗, 其实并不是, 因为一般来讲正常的请求当然都是缓存中没有值, 请求数据库后把值缓存到redis中, 那么什么是缓存穿透及什么情况下会发生缓存穿透请继续往下看
什么是缓存穿透及什么情况下会发生缓存穿透: 首先正常系统出现缓存穿透的情况并不多见, 一般来看其是一种攻击手段, , 攻击者用缓存及数据库中不存在的值发动攻击请求, 由于redis中及数据库中都不存在这个值, 那么这个攻击请求就会绕开redis持续对数据库增加负载, 直到数据库崩溃, 其实这就叫缓存穿透
如何解决缓存穿透: 目前比较流行就是加布隆过滤器, 这玩意听着高大上其实是非常简单的东西, 自己使用hashSet也可以实现, 其原理就是事先把key放到布隆过滤器中, 然后收到请求的时候, 先查一下布隆过滤器中key是否存在, 如存在继续(查redis、查数据库), 如不存在则直接返回
布隆过滤器实现举例:
@Service
public class ProductService {@Autowiredprivate BloomFilter<String> productBloomFilter;@Autowiredprivate ProductRepository productRepository;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private static final String PRODUCT_CACHE_PREFIX = "product:";private static final long CACHE_EXPIRE_SECONDS = 3600; // 1小时/*** 初始化布隆过滤器*/@PostConstructpublic void initBloomFilter() {// 初始化时加载所有存在的ID到布隆过滤器List<Long> allIds = productRepository.findAllIds();allIds.forEach(id -> productBloomFilter.put(PRODUCT_CACHE_PREFIX + id));}/*** 布隆过滤器应对缓存穿透问题*/public Product getProductWithBloomFilter(Long id) {String cacheKey = PRODUCT_CACHE_PREFIX + id;// 1. 布隆过滤器检查if (!productBloomFilter.mightContain(cacheKey)) {throw new RuntimeException("Product not exists");}// 2. 正常缓存查询流程return getProductById(id);}public Product getProductById(Long id) {// 1. 先查Redis缓存String cacheKey = PRODUCT_CACHE_PREFIX + id;Product product = (Product) redisTemplate.opsForValue().get(cacheKey);if (product != null) {return product;}// 2. Redis没有则查数据库product = productRepository.findById(id).orElseThrow(() -> new RuntimeException("Product not found"));// 3. 写入Redis并设置过期时间redisTemplate.opsForValue().set(cacheKey, product, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);return product;}
}
2. 缓存击穿
缓存击穿定义及发生情况: 某个热点key在缓存中过期失效的瞬间,同时有大量并发请求访问这个key,导致所有请求都直接打到数据库上,造成数据库瞬时压力激增甚至崩溃的现象
如何解决缓存击穿: 可以设置热点key永不过期, 或者使用提前预热的方式来解决缓存击穿问题, 也可以使用互斥锁只允许一个线程重建缓存
互斥锁的实现举例如下:
public Product getProductWithLock(Long id) {String cacheKey = "product:" + id;// 1. 先查缓存Product product = redis.get(cacheKey);if (product != null) {return product;}// 2. 获取分布式锁String lockKey = "lock:product:" + id;try {boolean locked = redisLock.tryLock(lockKey, 3, TimeUnit.SECONDS);if (!locked) {// 获取锁失败,短暂等待后重试Thread.sleep(100);return getProductWithLock(id);}// 3. Double Check(防止其他线程已经重建缓存)product = redis.get(cacheKey);if (product != null) {return product;}// 4. 查数据库product = db.getProduct(id);if (product == null) {// 应对缓存穿透,设置空值(如果已经设置布隆过滤器此步骤可省略)redis.set(cacheKey, new NullValue(), 5, TimeUnit.MINUTES);} else {// 5. 写入缓存redis.set(cacheKey, product, 1, TimeUnit.HOURS);}return product;} catch (InterruptedException e) {Thread.currentThread().interrupt();throw new RuntimeException(e);} finally {redisLock.unlock(lockKey);}
}
3.缓存雪崩
缓存雪崩字面意思: 雪崩顾名思义为大片大片的雪坍塌导致, 那么缓存雪崩也就是大面积的缓存数据过期, 导致多个请求突然打到数据库中, 导致数据库崩溃所致
**缓存雪崩什么情况下会发生: ** Redis缓存数据批量过期或Redis宕机
如何解决缓存雪崩: 一般来说避免多个key设置同一个过期时间, 对每个key设置差异化过期时间
当然这三个问题也有其他响应解决办法, 本文意在说明三个名词的区别及方便大家记忆其他不在赘述, 感谢大家观看 !