一、思路
1、在实际开发中,我们需要对多个业务进行数据的缓存,并且要处理缓存带来的缓存穿透、雪崩、击穿问题,所以我们将这些缓存操作以及缓存的解决方案通过编写一个Redis工具类进行封装起来,提高代码的复用性,降低代码的耦合。
二、业务逻辑
1. 将任意java对象序列化成json并保存到Redis中,并设置TTL过期时间
public void set(String key, Object value, Long time, TimeUnit unit){
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
}
2. 将任意java对象序列化成json并保存到Redis中,并设置逻辑过期时间,用于处理缓存击穿问题
public void setWithLogicalExpire(String key,Object value,Long time,TimeUnit unit){
//设置逻辑过期时间,并封装数据
RedisData redisData = new RedisData();
redisData.setData(value);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
//写入Redsi中
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));
}
3. 根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值来解决缓存穿透问题
public <R,ID> R saveWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbfollback,Long time,TimeUnit unit){
//根据ID查询Redis中是否存在
String key = keyPrefix+id;
String json = stringRedisTemplate.opsForValue().get(key);
//Redis有,则直接返回
//isNotBlank:不为null、不为""、length!=0
if (StringUtils.isNotBlank(json)){
//将json数据转为java对象
return JSONUtil.toBean(json, type);
}
//判断查询到的是否为空值""
if ("".equals(json)){
return null;
}
//不存在,则查询数据库
R r = dbfollback.apply(id);
if (r == null){
//数据库中没有,则缓存空值到Redis中
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
//数据库有,则将查询到的数据保存到Redis中
this.set(key,r,time,unit);
//返回给前端
return r;
}
4. 根据指定的key查询缓存,并反序列化为指定类型,利用逻辑过期来解决缓存击穿问题
//创建线程池
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public <R,ID> R saveWithLogicalExpire(String keyPrefix,ID id,Class<R> type,Function<ID,R> dbfollback,Long time,TimeUnit unit){
//从Redis中查询商铺信息
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
//判断是否存在
if (StrUtil.isBlank(json)){
//不存在
return null;
}
//存在,则需要先将json反序列化为java对象
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
//判断缓存是否过期
LocalDateTime expireTime = redisData.getExpireTime();
if (expireTime.isAfter(LocalDateTime.now())){
//未过期,则直接返回信息
return r;
}
//已过期,则重建缓存
//获取互斥锁
String lockKey = CACHE_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
//判断是否获取成功
if (isLock){
//成功,则开启独立线程,进行缓存重建
CACHE_REBUILD_EXECUTOR.submit(() -> {
//查询数据库
R rnew = dbfollback.apply(id);
//写入缓存,并设置逻辑过期
this.setWithLogicalExpire(key,rnew,time,unit);
//释放锁
unLock(lockKey);
});
}
//失败,则返回过期的数据
return r;
}
//获取互斥锁
public boolean tryLock(String key){
//设置互斥锁
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "lock", LOCK_SHOP_TTL, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
//释放锁
public void unLock(String key){
stringRedisTemplate.delete(key);
}