黑马点评第一阶段商户查询缓存(中风头疼。。。)

  1. 写工具类时,不确定的数据类型以及实体类要善于用泛型表示,由调用者来告诉我们真实的类型是什么,从而做出泛型的推断
  2. 涉及到数据库查询的时候可以使用函数式编程,还是由调用者来告诉我们数据库查询的方式,那数据库查询也是一个有参数和返回值的函数所以可以使用Function<T,R>指定参数和返回值类型,在调用者调用的时候指定即可
 //调用者
Shop shop = cacheClient.queryWithLogicExpire(
RedisConstants.CACHE_SHOP_KEY,
id,
20L,
TimeUnit.SECONDS,
Shop.class,
this::getById);  数据库执行的函数 有一个参数和一个返回值


public <R,ID> R queryWithLogicExpire(String keyPrefix, ID id, Long expireTime, TimeUnit timeUnit, Class<R> type, Function<ID,R> databaseFallBack) //ID,R 参数类型,返回值类型
{
         ....
                           
         R selectR = databaseFallBack.apply(id);
         ....
}

更通俗一点就是 可以把调用者的 this::getById 替换成
ID -> this.getById(ID);  

1.缓存穿透:

指缓存和数据库中都没有的数据,而用户不断发起请 求,如发起为id“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

2. 缓存击穿

某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进
来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬
时数据库请求量大、压力骤增,甚至可能打垮数据库。

课程中学习到了可以通过互斥锁和对空值缓存同时解决两者的问题

public Shop queryWithPassMutex(Long id){
        //1.从redis查询商户缓存
//        Map<Object, Object> shopMap = stringRedisTemplate.opsForHash().entries(RedisConstants.CACHE_SHOP_KEY + id);
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        String shopString = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopString)) {
            //3.存在
            return JSONUtil.toBean(shopString, Shop.class);
        }
        //判断空值
        if (shopString != null){
            //是空字符串
            return null;
        }
 
        Shop shop = null;
        String lockKey = RedisConstants.LOCK_SHOP_KEY +id;
        try {
            if (!tryLock(lockKey)) {
                //获取锁失败 重复递归执行,获取缓存信息
                System.out.println("-------------Lock Fail");
                Thread.sleep(300);
                return queryWithPassMutex(id);
            }
            System.out.println("-----------------Lock Success");
            //4.数据库查询
            LambdaQueryWrapper<Shop> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Shop::getId,id);
            shop = shopMapper.selectOne(queryWrapper);
            //模拟重建延时
            Thread.sleep(200);
            if (shop==null) {
                                                                            //同时解决缓存穿透,这个数据在数据库中也不存在,防止恶意攻击
                stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,"",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //查询有结果  写入redis中
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(shop),RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);

        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            //释放互斥锁
            loseLock(lockKey);
        }
        return shop;
    }

对于缓存击穿还有通过设置逻辑过期时间的方法来解决

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

    //缓存击穿   通过设置逻辑过期时间
    public Shop queryWithLogicExpire(Long id,Long expireTime){
        //1.从redis查询商户缓存
//        Map<Object, Object> shopMap = stringRedisTemplate.opsForHash().entries(RedisConstants.CACHE_SHOP_KEY + id);
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        String shopJsonInfo = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isBlank(shopJsonInfo)) {
            //3.未命中缓存
            return null;
        }

        //命中 JSON反序列化
        RedisData redisData = JSONUtil.toBean(shopJsonInfo, RedisData.class);
        //类型为OBject 所以要先转为JSONObject
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
        LocalDateTime dataExpireTime = redisData.getExpireTime();

        //判断是否过期  过期:缓存重建  未过期:返回店铺信息
        if (dataExpireTime.isAfter(LocalDateTime.now())){
            log.info("未过期");
            return shop;
        }

        //缓存重建  "expireTime": 1691030052865
        //获取互斥锁
        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;

            //判断是否拿到锁
            //true:开启线程 实现缓存重建     false:返回旧的店铺信息
            if (tryLock(lockKey)){
                log.info("已过期");
                CACHE_REBUILD_EXECUTOR.submit( () ->{
                    try {
                        //重建
                        this.saveShop2Redis(id, expireTime);
                    }catch (Exception e){
                        throw new RuntimeException(e);
                    }finally {
                        //释放锁
                        loseLock(lockKey);
                    }
                });
            }
        return shop;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值