对实战二中的 缓存击穿 和 缓存穿透 进行封装成工具类
工具类代码如下:
@Data
@Component
public class RedisCacheUtils {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private final static ExecutorService CACHE_REBULID_EXECUTOR= Executors.newFixedThreadPool(10);
//设置set方法
public void set(String key, Object value, Long time, TimeUnit unit){
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(value),time,unit);
}
//设置逻辑过期
public void setLogicExpire(String key, Object value,Long time,TimeUnit unit){
//设置逻辑过期
RedisData redisData = new RedisData(value, LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
//尝试锁
public boolean tryLock(String key,Long time,TimeUnit unit){
Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(key, "", time, unit);
return isLock;
}
//释放锁
public void unLock(String key ){
Boolean isLock = stringRedisTemplate.delete(key);
}
public String get(String key){
String value = stringRedisTemplate.opsForValue().get(key);
return value;
}
//todo 赋空值或者默认值 解决 缓存穿透
public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> typeR,
Function<ID,R> dbFallBack, Long time, TimeUnit unit) {
/**
* 首先在redis的缓存中查,redis缓存中有则直接返回数据
* redis缓存中没有,再去数据库中查;
* 数据库中查到后在,将查到的数据写到redis缓存中去
* 并且将查到的数据返回
*/
String key=keyPrefix+id;
String stringJson = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isNotBlank(stringJson)){
R r = JSONUtil.toBean(stringJson, typeR);
return r;
}
if(stringJson!=null)
//判断是空值,就是redis为热键设置的,防止多次访问数据库
return null;
R r=dbFallBack.apply(id);
if(r==null){
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
}
else
this.set(key,r,time,unit);
return r;
}
//todo 逻辑过期 解决 缓存击穿
public <R,ID > R queryWithLogicExpire(String keyPrefix,String keyLockPrefix, ID id, Class<R> typeR
, Function<ID,R> dbFallBack, Long time, TimeUnit unit){
String key = keyPrefix+id;
String redisDataString = this.get(key);
if(StrUtil.isBlank(redisDataString))
return null;
RedisData rsd = JSONUtil.toBean(redisDataString, RedisData.class);
JSONObject object=(JSONObject) rsd.getData();
R r = BeanUtil.toBean(object,typeR);
LocalDateTime expireTime = rsd.getExpireTime();
if(expireTime.isAfter(LocalDateTime.now()))
return r;
String lockKey=keyLockPrefix+id;
boolean isLock = this.tryLock(lockKey, 3l, TimeUnit.SECONDS);
if(isLock){
CACHE_REBULID_EXECUTOR.submit(()->{
try {
System.out.println("线程开始了");
R r1 = dbFallBack.apply(id);
this.setLogicExpire(key,r1,time,unit);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
this.unLock(lockKey);
}
});
}
return r;
}
//todo 使用互斥锁 解决 缓存击穿
public <R,ID> R queryWithMutex(String keyCachePrefix,String keyLockPrefix,ID id,Class<R> typeR,Function<ID,R> dbFallBack,Long cacheTime, TimeUnit cacheUnit,Long lockTime, TimeUnit lockUnit ){
/**
* 首先在redis的缓存中查,redis缓存中有则直接返回数据
* redis缓存中没有,再去数据库中查;
* 数据库中查到后在,将查到的数据写到redis缓存中去
* 并且将查到的数据返回
*/
String key=keyCachePrefix + id;
String stringJson = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isNotBlank(stringJson)){
R r = JSONUtil.toBean(stringJson, typeR);
return r;
}
if(stringJson!=null)
//判断是空值,就是redis为热键设置的,防止多次访问数据库
return null;
//todo 实现缓存重建
//1. 获取互斥锁
//2. 判断是否获取成功
//3. 失败则休眠并重试,成功则根据id查询并将查询内容写入redis
//4. 释放互斥锁
String lockKey=keyLockPrefix+id;
R r=null;
try {
boolean isLock = tryLock(lockKey,lockTime,lockUnit);
if(!isLock){
//获取失败则休眠并重新获取锁
Thread.sleep(50);
return queryWithMutex(keyCachePrefix,keyLockPrefix,id,typeR,dbFallBack,cacheTime,cacheUnit,lockTime,lockUnit);
}
r = dbFallBack.apply(id);
//模拟重建的延时
Thread.sleep(300);
if(r==null){
stringRedisTemplate.opsForValue().set(key,"",cacheTime,cacheUnit);
}
else
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),cacheTime, cacheUnit);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
unLock(lockKey);
}
return r;
}
}