Redis实战——商户查询(三)

封装工具类

今天主要是对商户查询的代码进行了工具类的封装,但是需要Redis实战——商户查询(二) 对这篇文章说明下错误的地方

  • Redis实战——商户查询(二) 问题

    • 逻辑过期解决缓存击穿中的流程图中,获取互斥锁失败后,不应该是返回过期数据,而是线程休眠一会儿

      Redis实战——商户查询(三)_第1张图片

    • 代码问题

      RedisData<Shop> redisData = JSONUtil.toBean(shopString, RedisData.class);
      Shop shop = redisData.getData();
      需要修改为
      RedisData redisData = JSONUtil.toBean(shopString, RedisData.class);
      Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
      
      shop = redisData.getData();
      需要修改为
      shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
      
  • 基于StringRedisTemplate封装一个缓存工具类

    • 将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
    • 将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题
    • 根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
    • 根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题
    • 根据指定的key查询缓存,并反序列化为指定类型,需要利用互斥锁解决缓存击穿问题
/**
 * 缓存工具类
 * @author liang
 */
@Component
public class CacheClient {


    private final StringRedisTemplate stringRedisTemplate;

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

    /**
     * 将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
     * @param key  redis中的键
     * @param value redis中的值
     * @param time 时间
     * @param unit 时间单位
     */
    private void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }



    /**
     *  将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题
     * @param key
     * @param value
     * @param time
     * @param unit
     * @param 
     */
    private  <T> void setWithLogicalExpire(String key, T value, Long time, TimeUnit unit){
        //封装value 设置逻辑过期时间
        RedisData<T> redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        //将数据写入redis中
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }


    /**
     * 根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
     * @param keyPrefix redis中存储的键的前缀
     * @param id 查询的id
     * @param clazz 查询的对象类型
     * @param function
     * @param time TTL过期时间
     * @param unit 过期时间单位
     * @return
     * @param 
     */
    public <T,ID> T queryWithPassThrough(String keyPrefix, ID id, Class<T> clazz, Function<ID, T> function,Long time, TimeUnit unit) {
        String key  = keyPrefix + id;
        // 在redis中查询商铺信息
        String s = stringRedisTemplate.opsForValue().get(key);
        // 如果从redis中获取到了数据 则直接返回
        if (StrUtil.isNotBlank(s)){
            return JSONUtil.toBean(s, clazz);
        }
        // 通过id 在数据库中查询
        T apply = function.apply(id);
        // 数据库中不存在该值
        if (ObjectUtil.isNull(apply)){
            //将空值封装到Redis中
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            //返回空
            return null;
        }
        //数据库中查询成功
        this.set(key,apply,time,unit);
        //返回数据
        return apply;
    }


    /**
     * 根据key 获取RedisData
     * @param key
     * @return
     */
    private RedisData getRedisData(String key) {
        String s = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(s)){
            return null;
        }
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        return redisData;
    }

    /**
     * 是否逻辑过期
     * @param redisData
     * @return true: 没有过期
     */
    private boolean LogicalOverdue(RedisData redisData){
        if (ObjectUtil.isNull(redisData)){
            throw new NullPointerException("RedisData对象不能为空");
        }
        LocalDateTime expireTime = redisData.getExpireTime();
        return expireTime.isAfter(LocalDateTime.now());
    }
    /**
     *  根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题
     * @param keyPrefix    redis中存储的键的前缀
     * @param lockKeyPrefix  redis中存储的锁的键的前缀
     * @param id
     * @param clazz   查询的对象类型
     * @param function
     * @param time 逻辑过期时间
     * @param unit 时间单位
     * @return
     * @param 
     * @param 
     */
    public <ID, T> T queryWithLogicalExpire(String keyPrefix,  String lockKeyPrefix, ID id, Class<T> clazz,
                                            Function<ID, T> function, Long time, TimeUnit unit){

        String key = keyPrefix + id;
        RedisData redisData = this.getRedisData(key);
        if (ObjectUtil.isNull(redisData)){
            return null;
        }
        T t = JSONUtil.toBean((JSONObject) redisData.getData(), clazz);
        //判断是否逻辑过期
        if (this.LogicalOverdue(redisData)) {
            //没有过期 返回数据
            return t;
        }
        String lockKey = lockKeyPrefix + id;
        try {
            // 过期
            //获取互斥锁
            boolean b = this.tryLock(lockKey);
            //获取锁失败
            if (!b){
                //线程休眠
                Thread.sleep(50);
                return queryWithLogicalExpire(keyPrefix, lockKeyPrefix, id, clazz, function, time, unit);
            }
            //获取锁成功应该进行再次检查redis缓存是否存在,即做DoubleCheck,如果存在则无需重建缓存
            redisData = this.getRedisData(key);
            if (ObjectUtil.isNull(redisData)){
                return null;
            }
            t = JSONUtil.toBean((JSONObject) redisData.getData(), clazz);
            //判断是否逻辑过期
            if (this.LogicalOverdue(redisData)) {
                //没有过期 返回数据
                return t;
            }
            //开启新线程
            executor.execute( ()-> {
                T apply = function.apply(id);
                this.setWithLogicalExpire(key, apply, time, unit);
            });

        }catch (Exception e){
            throw new RuntimeException();
        }finally {
            this.unlock(lockKey);
        }

        return t;
    }

    /**
     * 根据key在redis中获取 对象
     * @param key
     * @param clazz
     * @return
     * @param 
     */
    private  <T > T getBeanByKey(String key, Class<T> clazz){
        String s = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(s)){

            T t = JSONUtil.toBean(s, clazz);
            System.out.println(t);
            return t;
        }
        return null;
    }

    /**
     *  根据指定的key查询缓存,并反序列化为指定类型,需要利用互斥锁解决缓存击穿问题
     * @param keyPrefix    redis中存储的键的前缀
     * @param lockKeyPrefix  redis中存储的锁的键的前缀
     * @param id
     * @param clazz   查询的对象类型
     * @param function
     * @param time 逻辑过期时间
     * @param unit 时间单位
     * @return
     * @param 
     * @param 
     */
    public <T, ID> T queryWithMutex(String keyPrefix,  String lockKeyPrefix, ID id, Class<T> clazz,
                                    Function<ID, T> function, Long time, TimeUnit unit){
        String key = keyPrefix + id;
        T t = this.getBeanByKey(key, clazz);
        if (ObjectUtil.isNotNull(t)){
            return t;
        }
        String lockKey = lockKeyPrefix + id;
        try{
            boolean b = tryLock(lockKey);
            if (!b){
                Thread.sleep(30);
                return queryWithMutex( keyPrefix,   lockKeyPrefix,  id,  clazz,
                         function,  time,  unit);
            }
            //获取锁成功应该进行再次检查redis缓存是否存在,即做DoubleCheck,如果存在则无需重建缓存
            t = this.getBeanByKey(key, clazz);
            if (ObjectUtil.isNotNull(t)){
                return t;
            }
            t = function.apply(id);
            if (ObjectUtil.isNull(t)){
                stringRedisTemplate.opsForValue().set(key,"", CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            this.set(key, t, time, unit);
        }catch (Exception e){

        }finally {
            unlock(lockKey);
        }
        return t;
    }

    private int corePoolSize = 10;//核心线程数
    private int maxPoolSize = 20;//最大线程数
    private long keepAliveTime = 60;//非核心线程的空闲时间
    private TimeUnit unit = TimeUnit.SECONDS;//时间单位
    private LinkedBlockingQueue<Runnable> workQuery = new LinkedBlockingQueue<>();//任务队列
    //创建线程池
    private ThreadPoolExecutor executor =  new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQuery);


    /**
     * 获取锁
     * @param key
     * @return
     */
    private boolean tryLock(String key){
        return stringRedisTemplate.opsForValue().setIfAbsent(key,"1", 10L,TimeUnit.SECONDS);
    }

    /**
     * 释放锁
     * @param key
     */
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }

}

本文由mdnice多平台发布

你可能感兴趣的:(redis,java)