1.缓存空值解决缓存穿透
1.1什么是缓存穿透
1.2解决方案
1.3代码实现
//缓存穿透解决方案
public Shop queryWithPassThough(Long id){
//从redis查询商铺缓存
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){//上面的isNotBlank方法会将空字符串或者null传递到下面,如果不是null那么就是空字符串
return null;
}
//不存在根据id查询
Shop shop = getById(id);
//不存在返回错误
if (shop==null){
//将空值写入redis //防止缓存穿透
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
//存在,写入redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
return shop;
}
2.缓存雪崩及其解决方案
3.解决缓存击穿
3.1什么是缓存击穿
3.2解决方案
3.3代码实现
3.3.1模拟互斥锁解决
private boolean tryLocal(String key){
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);//防止拆箱。出现空指针
}
private void unLocal(String key){
stringRedisTemplate.delete(key);
}
//缓存击穿解决方案
public Shop queryWithMutex(Long id){
//从redis查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//判断是否存在
if (StrUtil.isNotBlank(shopJson)) {
//存在直接返回
return JSONUtil.toBean(shopJson, Shop.class);
}
//判断是否命中空值 //防止缓存穿透
if (shopJson !=null){//上面的isNotBlank方法会将空字符串或者null传递到下面,如果不是null那么就是空字符串
return null;
}
//实现缓存重建
//获取互斥锁
String localKey=LOCK_SHOP_KEY+id;
Shop shop=null;
try {
boolean isLocal = tryLocal(localKey);
//判断是否获取成功
if (!isLocal){
//失败,则休眠并重试
Thread.sleep(50);
return queryWithMutex(id);
}
//成功,根据id查询
//再检测redis缓存是否存在,DoubleCheck
//??
shop = getById(id);
//不存在返回错误
if (shop==null){
//将空值写入redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
//存在,写入redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
}catch (InterruptedException e){
throw new RuntimeException(e);
}finally {
//释放互斥锁
unLocal(localKey);
}
//返回
return shop;
}
3.3.2逻辑过期解决
private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);//线程池
//缓存击穿解决方案--逻辑过期
public Shop queryWithLogicalExpire(Long id){
//从redis查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//判断是否存在
if (StrUtil.isBlank(shopJson)) {
//不存在存在直接返回空
return null;
}
//命中,先把json反序列化为对象
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
LocalDateTime expireTime = redisData.getExpireTime();
//判断是否过期
if (expireTime.isAfter(LocalDateTime.now())){
//未过期,直接返回店铺信息
return shop;
}
//过期,需要缓存重建
//缓存重建
//获取互斥锁
String lockKey=LOCK_SHOP_KEY+id;
boolean isLock = tryLocal(lockKey);
//是否获取成功
if (isLock){
//成功,开启独立线程,实现缓存重建
CACHE_REBUILD_EXECUTOR.submit(()->{
//重建
try {
saveShop2Redis(id,20L);
}catch (Exception e){
throw new RuntimeException(e);
}finally {
//释放锁
unLocal(lockKey);
}
});
}
//失败,返回过期商铺信息
return shop;
}
内容来自黑马程序员-Redis入门到实战