缓存击穿是指部分热点key缓存失效的情况,我的解决办法是给缓存加上逻辑上的过期时间,如果到了过期时间则第一个拿到锁的人就会进行缓存的重构,该过程是新开一个独立的线程进行操作,其他人则直接返回旧数据,并且默认情况下redis缓存中是有热点信息的,所以不需要进行非空的判断,我们加的锁是在redis中进行的唯一key会返回操作成功或者失败的原理进行操作的
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
/**
* 缓存击穿逻辑过期
* @param prefix
* @param lockPrefix
* @param id
* @param type
* @param dbFallback
* @param time
* @param unit
* @return
* @param <R>
* @param <ID>
*/
//用逻辑过期解决热点问题默认是存在redis中的
public <R,ID> R queryWithLogicalExpire(String prefix,String lockPrefix,ID id,Class<R> type, Function<ID,R> dbFallback,Long time, TimeUnit unit ) {
String key = prefix+id;
//从redis中查询数据
String json = stringRedisTemplate.opsForValue().get(key);
//没有的话直接结束,因为热点数据是自己添加的,默认是有的
if (StrUtil.isBlank(json)) {
return null ;
}
//4.命中数据后,需要将json数据反序列化
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
JSONObject jsonObject = (JSONObject) redisData.getData();
R r = JSONUtil.toBean(jsonObject, type);
LocalDateTime expireTime = redisData.getExpireTime();
//5判断商品是否过期
if(expireTime.isAfter(LocalDateTime.now())) {
//5.1未过期,直接返回数据
return r;
}
//5.2已过期,需要缓存重建
//6缓存重建
String lockKey = lockPrefix+id;
//6.1获取互斥锁
boolean isLock = tryLock(lockKey);
//6.2获取锁是否成功
if (isLock) {
//6.3成功,需要开启独立线程,进行缓存重建
CACHE_REBUILD_EXECUTOR.submit(() ->{
//重建缓存
try {
//查询数据库
R r1 = dbFallback.apply(id);
//存入redis
this.setWithLogicExpire(key, r1, time, unit);
}
catch (Exception e){
throw new RuntimeException();
}
finally {
//释放锁
unlock(lockKey);
}
});
}
//6.4失败了,缓存过期的商品信息
return r;
}
//做缓存击穿后,进行锁的互斥
//获取锁
private boolean tryLock(String lock) {
Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(lock, "1", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(b);
}
//释放锁
private void unlock(String lock) {
stringRedisTemplate.delete(lock);
}