Redis框架(九):大众点评项目 缓存工具封装

大众点评项目 缓存工具封装

SpringCloud章节复习已经过去,新的章节Redis开始了,这个章节中将会回顾Redis实战项目 大众点评
主要依照以下几个原则

  1. 基础+实战的Demo和Coding上传到我的代码仓库
  2. 在原有基础上加入一些设计模式,stream+lamdba等新的糖
  3. 通过DeBug调试,进入组件源码去分析底层运行的规则和设计模式

代码会同步在我的gitee中去,觉得不错的同学记得一键三连求关注,感谢:
Redis优化-链接: RedisUnitToolProject
在这里插入图片描述

需求:缓存工具封装

我们已经在上面三节处理了各种细节:缓存穿透、缓存击穿、缓存雪崩

我们在开发过程中,如果每次缓存操作都处理一遍,那太繁琐了

为了开发的流畅和严谨,我们在这一节整合以往的四种处理方案,将其封装成工具类,这样就可以直接调工具,而不是RedisTemplate

在这里插入图片描述

业务实现

其实代码和上几节基本一直,但是为了封装处理, 需要回顾线程池、函数式编程接口 和泛型使用

@Slf4j
@Component
public class CacheClient {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //需要回顾线程池、函数式编程接口 和泛型使用
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

定义好对应的属性


    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){
        RedisData redisData = new RedisData();
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        redisData.setObject(value);
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

定义好对应的两种存储方法,分别是通过为了 缓存穿透, 缓存击穿

缓存穿透


    //通过泛型来进行处理不可知的类型,通过函数式编程传入IT用户的查询信息
    public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
                                          Function<ID, R> dbFallback, Long time, TimeUnit unit){
        log.debug("RedisTool method...");
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            return JSONUtil.toBean(json, type);
        }

        if(json!=null){
            return null;
        }

        //数据库查询怎么办,通过传入函数式编程处理
        R r = dbFallback.apply(id);

        if(r==null){
            stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }

        this.set(key, r, time, unit);

        return r;
    }

逻辑过期时间处理缓存击穿

    // 逻辑过期时间 通过线程池进行处理
    public <ID, R> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
                                         Function<ID, R> dbFallback, Long time, TimeUnit unit){
        log.debug("RedisTool queryWithLogicalExpire Method...");
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isBlank(json)){
            return null;
        }

        RedisData data = JSONUtil.toBean(json, RedisData.class);
//        Shop shop = JSONUtil.toBean((JSONObject) data.getObject(), Shop.class);
        JSONObject object = (JSONObject) data.getObject();
        R r = JSONUtil.toBean(object, type);

        if(data.getExpireTime().isAfter(LocalDateTime.now())){
            return r;
        }

        //过期了,就通过独立线程 -> 互斥锁处理缓存击穿
        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
        if (!tryLock(lockKey)) {
            log.debug("lockKey: " + lockKey + " Result: " + r);
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    this.saveShop2Redis(id,  dbFallback, keyPrefix,  time,  unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                    /*e.printStackTrace();*/
                }finally {
                    //删除暂时的key, 释放互斥锁
                    stringRedisTemplate.delete(lockKey);
                }
            });
        }

        if(r==null){
            return null;
        }

        return r;
    }

    public boolean tryLock(String key){

        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);
        //这里不可以直接返回flag,因为在拆箱过程中可能出现flag为null的情况;
        return BooleanUtil.isTrue(flag);
    }

    public <ID, R> void saveShop2Redis(ID id,
                                       Function<ID, R> dbFallback,
                                       String keyPrefix,
                                       Long time,
                                       TimeUnit unit){

        R r = dbFallback.apply(id);
        setWithLogicalExpire(keyPrefix + id,  r, time, unit);

    }

}

这样我们就可以直接去调用了,

queryWithLogicalExpire

    @Override
    public Result queryById(Long id) {

        Shop shop = cacheClient.queryWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById,
                RedisConstants.CACHE_NULL_TTL, TimeUnit.HOURS);

        if(shop==null){
            return Result.fail("店铺不存在");
        }

        return Result.ok(shop);
    }

queryWithPassThrough

    public Result queryById(Long id) {
/*        Shop shop = cacheClient.queryWithPassThrough(key, id, Shop.class, idf -> getById(idf),
                RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);*/

        Shop shop = cacheClient.queryWithPassThrough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById,
                RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);

        if(shop==null){
            return Result.fail("店铺不存在");
        }

        return Result.ok(shop);
    }

代码总览

@Slf4j
@Component
public class CacheClient {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //需要回顾线程池、函数式编程接口
    // 和泛型使用
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);



    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){
        RedisData redisData = new RedisData();
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        redisData.setObject(value);
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }


    //通过泛型来进行处理不可知的类型,通过函数式编程传入IT用户的查询信息
    public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
                                          Function<ID, R> dbFallback, Long time, TimeUnit unit){
        log.debug("RedisTool method...");
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            return JSONUtil.toBean(json, type);
        }

        if(json!=null){
            return null;
        }

        //数据库查询怎么办,通过传入函数式编程处理
        R r = dbFallback.apply(id);

        if(r==null){
            stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }

        this.set(key, r, time, unit);

        return r;
    }

    // 逻辑过期时间 通过线程池进行处理
    public <ID, R> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
                                         Function<ID, R> dbFallback, Long time, TimeUnit unit){
        log.debug("RedisTool queryWithLogicalExpire Method...");
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isBlank(json)){
            return null;
        }

        RedisData data = JSONUtil.toBean(json, RedisData.class);
//        Shop shop = JSONUtil.toBean((JSONObject) data.getObject(), Shop.class);
        JSONObject object = (JSONObject) data.getObject();
        R r = JSONUtil.toBean(object, type);

        if(data.getExpireTime().isAfter(LocalDateTime.now())){
            return r;
        }

        //过期了,就通过独立线程 -> 互斥锁处理缓存击穿
        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
        if (!tryLock(lockKey)) {
            log.debug("lockKey: " + lockKey + " Result: " + r);
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    this.saveShop2Redis(id,  dbFallback, keyPrefix,  time,  unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                    /*e.printStackTrace();*/
                }finally {
                    //删除暂时的key, 释放互斥锁
                    stringRedisTemplate.delete(lockKey);
                }
            });
        }

        if(r==null){
            return null;
        }

        return r;
    }

    public boolean tryLock(String key){

        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);
        //这里不可以直接返回flag,因为在拆箱过程中可能出现flag为null的情况;
        return BooleanUtil.isTrue(flag);
    }

    public <ID, R> void saveShop2Redis(ID id,
                                       Function<ID, R> dbFallback,
                                       String keyPrefix,
                                       Long time,
                                       TimeUnit unit){

        R r = dbFallback.apply(id);
        setWithLogicalExpire(keyPrefix + id,  r, time, unit);

    }

}

总结

在这里插入图片描述

测试一下

在这里插入图片描述
suc

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值