缓存(Redis)工具类,包含缓存击穿、缓存穿透、生成全局唯一id的解决方法

缓存(Redis)工具类,包含缓存击穿、缓存穿透、生成全局唯一id的解决方法

/**
 * 缓存(Redis)工具类,包含缓存击穿、缓存穿透、生成全局唯一id的解决方法
 * */
public class CacheManipulate {
     //生成全局id所需变量
    public static final long BEGIN_TIMESTEMP =
            LocalDateTime.of(2000,1,1,0,0).toEpochSecond(ZoneOffset.UTC);
    public static final int MOVE_BIT = 32;
    
    //注入redis
    private final StringRedisTemplate stringRedisTemplate;

    //创建一个线程池
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    
    public CacheManipulate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }


    //获取锁
    private boolean getLock(String key){
        try {
            Boolean valid = stringRedisTemplate.opsForValue().setIfAbsent(key,"1",10,TimeUnit.SECONDS);
            return BooleanUtil.isTrue(valid);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
    //解锁
    private void unlock(String key){
        try {
            stringRedisTemplate.delete(key);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
    /**
     * 设置具体过期时间
     * */
    public void set(String key, Object val, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(val),time,unit);
    }

    /**
     * 设置逻辑过期时间,不直接删除
     */
    public void setLogicalExpire(String key, Object val, Long time, TimeUnit unit){
        RedisData redisData = new RedisData();
        redisData.setData(val);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData),time,unit);
    }
    /**
     * 解决缓存穿透,数据库中不存在数值,需要设置空值
     * */
    public<T,R> R quaryWithExpire(String keyPrefix, T id, Class<R> type,
                                  Function<T,R> dbRollBack,Long time,TimeUnit unit){
        R res;
        String key = keyPrefix+ id.toString();
        //1、查询redis中是否存在
        String json = stringRedisTemplate.opsForValue().get(key);
        if(!StrUtil.isBlank(json)){
            res = JSONUtil.toBean(json,type);
            return res;
        }
        //isBlank函数当json为null时也会返回true
        if(json == null){
            return null;
        }
        //查询数据库
        res = dbRollBack.apply(id);
        if(res==null){
            //缓存空值
            this.set(key,"",time,unit);
        }else{
            //重建缓存
            this.set(key,res,time,unit);
        }
        return res;
    }

    /**
     * 解决缓存击穿,大量热点数据同时过期,需要重建缓存,逻辑过期时间
     * */
    public<T,R> R quaryWithLogicalExpire(String keyPrefix, T id, Class<R> type,
                                  Function<T,R> dbRollBack,Long time,TimeUnit unit) {
        R res=null;
        String key = keyPrefix+ id.toString();
        //1、查询redis中是否存在
        res = isExistKey(key,type);
        if(res != null)return res;

        //已过期重建缓存
        //获取锁
        String lockKey = RedisLockConstants.GLOBAL_LOCK_KEY+id.toString();
        Boolean isLock = getLock(lockKey);
        if(isLock){
            //双重校验防止多次重建
            res = isExistKey(key,type);
            if(res != null)return res;
            //启动一个线程去重建缓存
            Future<R> future = CACHE_REBUILD_EXECUTOR.submit(()->{
                R r;
                try {
                    //查询数据库
                    r = dbRollBack.apply(id);
                    if(r==null){
                        //缓存空值,可能不需要?
                        this.setLogicalExpire(key,"",time,unit);
                    }else{
                        //重建缓存
                        this.setLogicalExpire(key,r,time,unit);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    unlock(lockKey);
                }
                return r;
            });
            try {
                res = future.get();
                return res; // 返回结果
            } catch (InterruptedException | ExecutionException e) {
                // 处理异常
                throw new RuntimeException(e);
            }
        }

        return res;
    }


    /**
     * 判断缓存中是否存在key
     * */
    public <R> R isExistKey(String key,Class<R> type){
        R res = null;
        String json = stringRedisTemplate.opsForValue().get(key);
        if(!StrUtil.isBlank(json)){
            RedisData redisData = JSONUtil.toBean(json, RedisData.class);
            LocalDateTime expireTime = redisData.getExpireTime();
            res = JSONUtil.toBean((JSONObject) redisData.getData(),type);
            if(!expireTime.isAfter(LocalDateTime.now())){
                //已过期返回null
                res=null;
            }
        }
        //isBlank函数当json为null时也会返回true
        if(json == null){
            res=null;
        }
        return res;
    }
    /**
     * 生成全局唯一id
     * @param prefixKey
     * @return id
     * */
    public long nextId(String prefixKey){
        //生成前31位的时间戳前缀
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timeStemp = nowSecond-BEGIN_TIMESTEMP;
        //生产后32位的自增后缀序列号,date为年月日,相同的服务每天的Key不同,同时可以根据key来检索出每日每月每年的服务数量
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        long count =stringRedisTemplate.opsForValue().increment("icr"+prefixKey+":"+date);
        //位运算构造全局id
        long id = (timeStemp<<MOVE_BIT)|count;
        return id;
    }
}