黑马点评第二个模块---商户查询缓存

时间:2025-03-10 11:38:41
@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; } }