Redis实战三之封装Redis工具类

对实战二中的 缓存击穿 和 缓存穿透 进行封装成工具类

工具类代码如下:

@Data
@Component
public class RedisCacheUtils {
    
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private final static ExecutorService CACHE_REBULID_EXECUTOR= Executors.newFixedThreadPool(10);
    //设置set方法
    public void set(String key, Object value, Long time, TimeUnit unit){
    
    
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(value),time,unit);
    }
    //设置逻辑过期
    public void setLogicExpire(String key, Object value,Long time,TimeUnit unit){
    
    
        //设置逻辑过期
        RedisData redisData = new RedisData(value, LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    //尝试锁
    public boolean tryLock(String key,Long time,TimeUnit unit){
    
    
        Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(key, "", time, unit);
        return isLock;
    }
    //释放锁
    public void unLock(String key ){
    
    
        Boolean isLock = stringRedisTemplate.delete(key);
    }
    public String get(String key){
    
    
        String value = stringRedisTemplate.opsForValue().get(key);
        return value;
    }

    //todo 赋空值或者默认值 解决 缓存穿透
    public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> typeR,
                                         Function<ID,R> dbFallBack, Long time, TimeUnit unit) {
    
    
        /**
         * 首先在redis的缓存中查,redis缓存中有则直接返回数据
         * redis缓存中没有,再去数据库中查;
         * 数据库中查到后在,将查到的数据写到redis缓存中去
         * 并且将查到的数据返回
         */
       String key=keyPrefix+id;
       String stringJson = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isNotBlank(stringJson)){
    
    
            R r = JSONUtil.toBean(stringJson, typeR);
            return r;
        }
        if(stringJson!=null)
            //判断是空值,就是redis为热键设置的,防止多次访问数据库
            return null;
        R r=dbFallBack.apply(id);
        if(r==null){
    
    
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
        }
        else
            this.set(key,r,time,unit);
        return r;
    }

    //todo 逻辑过期 解决 缓存击穿
    public <R,ID > R queryWithLogicExpire(String keyPrefix,String keyLockPrefix, ID id, Class<R> typeR
                                        , Function<ID,R> dbFallBack, Long time, TimeUnit unit){
    
    
        String key = keyPrefix+id;
        String redisDataString = this.get(key);
        if(StrUtil.isBlank(redisDataString))
            return null;
        RedisData rsd = JSONUtil.toBean(redisDataString, RedisData.class);
        JSONObject object=(JSONObject) rsd.getData();
        R r = BeanUtil.toBean(object,typeR);
        LocalDateTime expireTime = rsd.getExpireTime();
        if(expireTime.isAfter(LocalDateTime.now()))
            return r;
        String lockKey=keyLockPrefix+id;
        boolean isLock = this.tryLock(lockKey, 3l, TimeUnit.SECONDS);
        if(isLock){
    
    
            CACHE_REBULID_EXECUTOR.submit(()->{
    
    
                try {
    
    
                    System.out.println("线程开始了");
                    R r1 = dbFallBack.apply(id);
                    this.setLogicExpire(key,r1,time,unit);
                } catch (Exception e) {
    
    
                    throw new RuntimeException(e);
                } finally {
    
    
                    this.unLock(lockKey);
                }
            });
        }
        return r;
    }

    //todo 使用互斥锁 解决 缓存击穿
    public <R,ID> R queryWithMutex(String keyCachePrefix,String keyLockPrefix,ID id,Class<R> typeR,Function<ID,R> dbFallBack,Long cacheTime, TimeUnit cacheUnit,Long lockTime, TimeUnit lockUnit ){
    
    
        /**
         * 首先在redis的缓存中查,redis缓存中有则直接返回数据
         * redis缓存中没有,再去数据库中查;
         * 数据库中查到后在,将查到的数据写到redis缓存中去
         * 并且将查到的数据返回
         */
        String key=keyCachePrefix + id;
        String stringJson = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isNotBlank(stringJson)){
    
    
            R r = JSONUtil.toBean(stringJson, typeR);
            return r;
        }
        if(stringJson!=null)
            //判断是空值,就是redis为热键设置的,防止多次访问数据库
            return null;
        //todo 实现缓存重建
        //1. 获取互斥锁
        //2. 判断是否获取成功
        //3. 失败则休眠并重试,成功则根据id查询并将查询内容写入redis
        //4. 释放互斥锁
        String lockKey=keyLockPrefix+id;
        R r=null;
        try {
    
    
            boolean isLock = tryLock(lockKey,lockTime,lockUnit);
            if(!isLock){
    
    
                //获取失败则休眠并重新获取锁
                Thread.sleep(50);
                return queryWithMutex(keyCachePrefix,keyLockPrefix,id,typeR,dbFallBack,cacheTime,cacheUnit,lockTime,lockUnit);
            }
            r = dbFallBack.apply(id);
            //模拟重建的延时
            Thread.sleep(300);
            if(r==null){
    
    
                stringRedisTemplate.opsForValue().set(key,"",cacheTime,cacheUnit);
            }
            else
                stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),cacheTime, cacheUnit);
        } catch (InterruptedException e) {
    
    
            throw new RuntimeException(e);
        } finally {
    
    
            unLock(lockKey);
        }
        return r;
    }
}

猜你喜欢

转载自blog.csdn.net/m0_51390725/article/details/128130680