基于StringRedisTemplate封装一个缓存工具类,满足下列需求:
方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
方法2:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题
方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题
CacheClient
java
@Slf4j
@Component
public class CacheClient {
private final StringRedisTemplate stringRedisTemplate;
/**
* 执行CacheClient相关业务逻辑。
*
* @param stringRedisTemplate stringRedisTemplate参数
* @return处理结果
*/
public CacheClient(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 将数据加入Redis,并设置有效期
*
* @param key缓存key
* @param value缓存数据值
* @param time有效时间
* @param unit有效时间单位
*/
public void set(String key, Object value, Long time, TimeUnit unit){
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
}
/**
* 写入带逻辑过期时间的缓存数据。
*
* 作用:
* 1.设置逻辑过期时间;
* 2.存入Redis;
*
* @param key Redis中的key
* @param value缓存数据
* @param time过期时间
* @param unit时间单位
* @return无返回值
*/
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)));
//存入Redis
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
/**
* 根据id查询数据(使用缓存空值法解决缓存穿透)
*
* @param keyPrefix缓存key前缀
* @param id查询id,与缓存key前缀拼接
* @param type查询数据的Class类型
* @param dbFallback根据id查询数据的函数式接口
* @param time有效期
* @param unit时间单位
* @param <R>
* @param <ID>
* @return
*/
//不知道将来返回什么类型,用泛型
/**
* 使用缓存空对象解决缓存穿透查询。
*
* 作用:
* 1.从Redis查询商铺缓存;
* 2.判断是否存在;
* 3.存在,直接返回;
* 4.判断命中的是否是空值;
* 5.返回一个错误信息;
*
* @param keyPrefix Rediskey前缀
* @param id业务id
* @param type返回值类型
* @param Function<ID Function<ID参数
* @param dbFallback数据库查询函数
* @param time过期时间
* @param unit时间单位
* @return处理结果
*/
public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time, TimeUnit unit){
String key= keyPrefix+id;
//1.从Redis查询商铺缓存
String Json=stringRedisTemplate.opsForValue().get(key);
//2.判断是否存在
if(StrUtil.isNotBlank(Json)){
//3.存在,直接返回
return JSONUtil.toBean(Json,type);
}
//判断命中的是否是空值
if(Json!=null){
//返回一个错误信息
return null;
}
//4.不存在,根据id查询数据库
R r=dbFallback.apply(id);
//5.不存在,返回错误
if(r==null){
//将空值写入Redis
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
//6.存在,写入Redis
this.set(key,r,time,unit);
//7.返回
return r;
}
private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
/**
* 根据id查询热点数据(使用逻辑过期解决缓存击穿)
*/
public <R,ID> R queryWithLogicalExpire(String keyPrefix,ID id,Class<R> type,Function<ID,R> dbFallback,Long time, TimeUnit unit){
String key= keyPrefix+id;
//1.从Redis查询商铺缓存
String Json=stringRedisTemplate.opsForValue().get(key);
//2.判断是否存在
if(StrUtil.isBlank(Json)){
//3.不存在,直接返回
return null;
}
//4.命中,需要先把Json反序列化为对象
RedisData redisData=JSONUtil.toBean(Json,RedisData.class);
R r= JSONUtil.toBean((JSONObject) redisData.getData(),type);
LocalDateTime expireTime=redisData.getExpireTime();
//5.判断是否过期
if(expireTime.isAfter(LocalDateTime.now())){
//5.1.未过期,直接返回店铺信息
return r;
}
//5.2.已过期,需要缓存重建
//6.缓存重建
//6.1.获取互斥锁
String lockKey=LOCK_SHOP_KEY+id;
boolean isLock=tryLock(lockKey);
//6.2.判断是否获取锁成功
if(isLock){
//6.3.成功,开启独立线程,实现缓存重建
CACHE_REBUILD_EXECUTOR.submit(()->{
try {
//重建缓存
//查询数据库
R r1=dbFallback.apply(id);
//存入Redis
this.setWithLogicalExpire(key,r1,time,unit);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//释放锁
unlock(lockKey);
}
});
}
//6.4.返回过期的商铺信息
return r;
}
/**
* 尝试获取Redis互斥锁。
*
* 作用:
* 1.原子命令:setlockvalueex10nx;
*
* @param key Redis中的key
* @return处理结果
*/
private boolean tryLock(String key){
// 原子命令:set lock value ex 10 nx
Boolean flag=stringRedisTemplate.opsForValue().setIfAbsent(key,"1",10,TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
/**
* 释放Redis互斥锁。
*
* @param key Redis中的key
* @return无返回值
*/
private void unlock(String key){
stringRedisTemplate.delete(key);
}
}- ShopServiceImpl
java
/**
* 根据id查询数据。
*
* 作用:
* 1.缓存穿透;
* 2.Shopshop=cacheClient.queryWithPassThrough(;
* 3.逻辑过期解决缓存击穿;
*
* @param id业务id
* @return处理结果
*/
@Override
public Result queryById(Long id) {
//缓存穿透
//Shop shop=cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id,Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.MINUTES);
//逻辑过期解决缓存击穿
Shop shop = cacheClient.queryWithLogicalExpire(CACHE_SHOP_KEY,id,Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.MINUTES);
if(shop == null){
return Result.fail("店铺不存在");
}
return Result.ok(shop);
}