封装Redis工具类
-
方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
-
方法2:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题
-
方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
-
方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题
@Component @Slf4j public class CacheClient { private StringRedisTemplate stringRedisTemplate; private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10); public CacheClient(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } //方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间 public void set(String key, Object value,Long time, TimeUnit unit){ String jsonStr = JSONUtil.toJsonStr(value); stringRedisTemplate.opsForValue().set(key,jsonStr,time,unit); } //方法2:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间, 用于处理缓存击穿问题 public void setWithLogicExpire(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)); } //方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题 public <R,ID> R queryWithPassThrought(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit timeUnit) { String key= keyPrefix +id; //1.从redis查询缓存数据 String json = stringRedisTemplate.opsForValue().get(key); //2.判断缓存数据是否存在 if (StringUtils.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,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES); //返回错误信息 return null; } //6.写入redis set(key,r,time,timeUnit); return r; } //方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题 public <R,ID> R queryWithLogicExpire(String keyPrefix,ID id,Class<R> type,Function<ID,R> dbFallback,Long time,TimeUnit timeUnit){ String key= keyPrefix +id; //1.从redis查 String json = stringRedisTemplate.opsForValue().get(key); //2.判断是否存在 if (StringUtils.isBlank(json)){ //不存在,直接返回 return null; } //3.命中,把json反序列化为对象 RedisData redisData = JSONUtil.toBean(json, RedisData.class); R r=JSONUtil.toBean((JSONObject) redisData.getData(),type); LocalDateTime expireTime = redisData.getExpireTime(); //4.查看是否过期 if (expireTime.isAfter(LocalDateTime.now())){ //未过期,直接返回 return r; } //5.已过期,缓存重建 //6.获取互斥锁 String lockKey=RedisConstants.LOCK_SHOP_KEY+id; boolean isLock = tryLock(lockKey); //判断是否获取锁成功 if (isLock){ CACHE_REBUILD_EXECUTOR.submit(()->{ try { //6.1查询数据库 R newR = dbFallback.apply(id); //6.2重建缓存 setWithLogicExpire(key,newR,time,timeUnit); } catch (Exception e) { throw new RuntimeException(e); } finally { //6.3释放锁 unlock(key); } }); } //7.获取锁失败,返回过期的信息 return r; } //方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用互斥锁解决缓存击穿问题 public <R, ID> R queryWithMutex(String keyPrefix,ID id,Class<R> type,Function<ID,R> dbFallback,Long time,TimeUnit timeUnit){ String key=keyPrefix+id; //1.从redis查询 String json = stringRedisTemplate.opsForValue().get(key); //2.判断是否存在 if (StringUtils.isNotBlank(json)){ //存在直接返回 return JSONUtil.toBean(json,type); } //3.判断命中的是否为空值 if (json!=null){ //返回一个错误信息 return null; } //4.未命中,尝试获取互斥锁 String lockKey=RedisConstants.LOCK_SHOP_KEY+id; R r = null; try { boolean isLock = tryLock(lockKey); // 4.1获取失败,休眠后继续获取 if (!isLock){ Thread.sleep(50); //递归查询 return queryWithMutex(keyPrefix, id, type,dbFallback, time, timeUnit); } //4.2获取成功,查询数据库 r = dbFallback.apply(id); //4.3不存在,返回错误 if (r==null){ //将空值写入redis stringRedisTemplate.opsForValue().set(key,"",time,timeUnit); //返回错误信息 return null; } //4.4存在,写回Redis set(key,JSONUtil.toJsonStr(r),time,timeUnit); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { unlock(lockKey); } //5.返回数据 return r; } private boolean tryLock(String lockKey) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } private void unlock(String lockKey){ stringRedisTemplate.delete(lockKey); } }