黑马点评第二个模块---商户查询缓存
@Slf4j
@Component
public class CacheClient {
private StringRedisTemplate stringRedisTemplate;
public CacheClient(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
* @param key
* @param value
* @param time
* @param unit
*/
public void set(String key, Object value, Long time, TimeUnit unit){
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(value),time,unit);
}
/**
*将任意Java对象序列化为json并存储在string类型的key中,并且key设置逻辑过期时间,用于处理缓存击穿问题
* @param key
* @param value
* @param time
* @param unit
*/
public void setWithLogicalExpire(String key,Object value,Long time,TimeUnit unit){
//设置逻辑过期
RedisData<Object> redisData =new RedisData<>();
redisData.setData(value);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
//写入redis
stringRedisTemplate.opsForValue().set(key,JSON.toJSONString(redisData));
}
/**
*
* @param keyPrefix key的前缀
* @param id
* @param type 具体的类型
* @param <R> 泛型 返回值
* @param <ID> 泛型 id
* @return
*/
//实现缓存穿透的工具
public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
Function<ID,R> dbFallback,Long time,TimeUnit unit) {
//1.从redis查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(keyPrefix + id);
//2.判断是否存在
if (StrUtil.isNotBlank(shopJson)) {
//3.存在,直接返回
return JSON.parseObject(shopJson, type);
}
//判断命中的是否是空值
if (shopJson != null) {
return null;
}
//4.不存在,返回id查询数据库
R shop = dbFallback.apply(id);
//5.不存在,返回错误
if (shop == null) {
//解决缓存穿透,将空值写入redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
//6.存在,写入redis
set(CACHE_SHOP_KEY + id,shop,time,unit);
//7.返回
return shop;
}
//定义一把锁
private boolean tryLocal(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "Lock", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
//释放锁
private void unLock(String key) {
Boolean flag = stringRedisTemplate.delete(key);
}
//自定义线程池
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
//实现缓存击穿的逻辑过期工具
public <R,ID> R queryWithLogicalExpire(String keyPrefix,ID id,Class<R> type,
Function<ID,R> dbFallBack,Long time,TimeUnit unit) {
//1.从redis查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//2.判断是否存在
if (StrUtil.isBlank(shopJson)) {
//3.不存在,直接返回
return null;
}
//4.命中,需要先把json反序列化为对象
RedisData<R> redisData = JSON.parseObject(shopJson,new TypeReference<RedisData<R>>(){});
R shop= redisData.getData();
LocalDateTime expireTime = redisData.getExpireTime();
//5.判断是否过期
if (expireTime.isAfter(LocalDateTime.now())){
//5.1未过期,直接返回店铺信息
return shop;
}
//5.2已过期,需要缓存重建
//6.缓存重建
//6.1判断是否获取锁成功
if (tryLocal(LOCK_SHOP_KEY+id)) {
//1.从redis查询商铺缓存
String shopJson2 = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//2.判断是否存在
if (StrUtil.isBlank(shopJson2)) {
//3.不存在,直接返回
return null;
}
//4.命中,需要先把json反序列化为对象
RedisData<R> redisData2 = JSON.parseObject(shopJson,new TypeReference<RedisData<R>>(){});
R shop2= redisData.getData();
LocalDateTime expireTime2 = redisData2.getExpireTime();
//5.判断是否过期
if (expireTime2.isAfter(LocalDateTime.now())){
//5.1未过期,直接返回店铺信息
return shop2;
}
//6.2成功,开启独立线程,实现缓存重建
CACHE_REBUILD_EXECUTOR.submit(()->{
try {
//重建缓存
R r1=dbFallBack.apply(id);
Thread.sleep(200);
//写入redis
setWithLogicalExpire(keyPrefix+id,r1,time,unit);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
//释放锁
unLock(LOCK_SHOP_KEY+id);
}
});
}
//6.3返回过期的商铺信息
return shop;
}
}