缓存穿透:指大量请求的数据在缓存和数据库都没有,这些请求全打在了数据库上
解决方案:
1、缓存空对象:把数据库不存在的数据也存入redis里面,并把值设为空对象
优点:实现简单,维护方便
缺点:
额外的内存消耗
可能造成短期的数据不一致
2、布隆过滤:如果通过布隆过滤判断数据不存在,那就是不存在,如果判断数据存在就不能判断真假
优点:内存占用较少,没有多余key
缺点:
实现复杂
存在误判可能
3、其他解决方法:
增强id的复杂度,避免被猜测id规律
做好数据的基础格式校验
加强用户权限校验
做好热点参数的限流
布隆过滤:布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,假设布隆过滤器判断这个数据不存在,则直接返回。
使用方法1的流程图
//缓存穿透设置空值
private Shop queryWithPassThrough(Long id){
String key = CACHE_SHOP_KEY+id;
//查看redis里面是否有商店数据
String shopJson = stringRedisTemplate.opsForValue().get(key);
//命中了
if(StrUtil.isNotBlank(shopJson)){
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return shop;
}
if(shopJson != null){
//返回一个错误信息
return null;
}
//redis里面不存在,根据id查询数据库
Shop shop = getById(id);
if(shop == null){
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
//存在写入redis
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
return shop;
}
缓存雪崩:同一时间大量在redis里面的key同时失效或者redis服务器宕机,导致大
量请求都打在了数据库上,带来巨大压力
解决方法:
-
给不同的Key的TTL添加随机值
-
利用Redis集群提高服务的可用性(防止一个redis宕机)
-
给缓存业务添加降级限流策略
-
给业务添加多级缓存
缓存击穿:也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key
突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
逻辑分析:假设线程1在查询缓存之后,本来应该去查询数据库,然后把这个数据重新加载到缓存的,此时只要线程1走完这个逻辑,其他线程就都能从缓存中加载这些数据了,但是假设在线程1没有走完的时候,后续的线程2,线程3,线程4同时过来访问当前这个方法, 那么这些线程都不能从缓存中查询到数据,那么他们就会同一时刻来访问查询缓存,都没查到,接着同一时间去访问数据库,同时的去执行数据库代码,对数据库访问压力过大。
解决方法:
互斥锁
逻辑过期
互斥锁:可以利用redis里面的setnx方法来表示获取锁,该方法含义是redis中如果没有这个key,则插入成功,返回1,在 stringRedisTemplate中返回true, 如果有这个key则插入失败,则返回0,在stringRedisTemplate返回false,我们可以通过true,或者是false,来表示是否有线程成功插入key,成功插入的key的线程我们认为他就是获得到锁的线程。
//使用redis里面的setnx作为锁
private boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
//删除锁
private void unlock(String key) {
stringRedisTemplate.delete(key);
}
//利用锁解决缓存击穿问题
private Shop queryWithMutex(Long id){
String key = CACHE_SHOP_KEY+ id;
//在redis里面查询是否存在数据
String shopJson = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isNotBlank(shopJson)){
// 存在直接返回
return JSONUtil.toBean(shopJson,Shop.class);
}
//命中但为无效值
if(shopJson != null){
return null;
}
String lockKey = LOCK_SHOP_KEY+id;
try {
//实现缓存重建
//redis不存在
//获取锁
boolean isLock = tryLock(lockKey);
if(!isLock){
//失败
//等待一段时间,回头重新执行
Thread.sleep(50);
return queryWithMutex(id);
}
//成功
//获取锁成功应再次判断redis里面是否已经有数据了,doubleCheck
shopJson = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isNotBlank(shopJson)){
// 存在直接返回
return JSONUtil.toBean(shopJson,Shop.class);
}
//命中但为空值
if(shopJson == null){
return null;
}
//查数据库,存入redis
Shop shop = getById(id);
if(shop == null){
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
return shop;
}catch (InterruptedException e){
throw new RuntimeException(e);
}finally {
//存在,直接放回数据
unLock(lockKey);
}