Redis:缓存击穿

缓存击穿(热点key): 部分key(被高并发访问且缓存重建业务复杂的)失效,无数请求会直接到数据库,造成巨大压力

1.互斥锁:可以保证强一致性

      线程一:未命中之后,获取互斥锁,再查询数据库重建缓存,写入缓存,释放锁

      线程二:查询未命中,未获得锁(已由线程一获得),等待一会,缓存命中

互斥锁实现方式:redis中setnx key value:改变对应key的value,仅当value不存在时执行,以此来实现互斥锁,防止出现锁得不到释放,设置有效期

public Shop queryWithMutex(Long id) throws InterruptedException {
        Shop shop;
        //实现互斥锁,解决缓存击穿
        String key=CACHE_SHOP_KEY+id;
        //1.从redis查询商铺缓存
        String shopJson=stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在,isNotBlank("")也为false
        if(StrUtil.isNotBlank(shopJson)){
            //3.存在,返回商铺对象
            return JSONUtil.toBean(shopJson,Shop.class);
        }
        //判断命中的是否为空值
        if(shopJson != null && shopJson.isEmpty()){
            return null;
        }
        //4.实现缓存重建
        String lockKey=LOCK_SHOP_KEY+id;
        //4.1.获取互斥锁
        boolean isLock=tryLock("lockKey");
        //4.2.判断互斥锁是否成功
        if(!isLock){
            //4.3.未成功,等待
                Thread.sleep(50);
            //递归
            shop=queryWithMutex(id);
        }
        else{
            //4.4.成功,从mysql数据库中查询
            shop=getById(id);
            //5.判断是否存在
            if(shop==null){
                //缓存空值,处理缓存穿透
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                return null;
            }
            //6.存在,向redis中缓存店铺数据

            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),LOGIN_USER_TTL, TimeUnit.MINUTES);
            //7.释放互斥锁
            unlock(lockKey);
        }
        //8.返回
        return shop;
    }

2.逻辑过期:

      不存TTL,添加上逻辑过期时间,判断逻辑上有没有过期,以此来更新数据

      线程一:查询缓存,逻辑已过期,获取互斥锁,开启新线程,返回过期数据

               新线程:查询数据库并重建缓存,重置逻辑过期时间,释放锁

      线程二:查询未命中,未获得锁(已由线程一获得),返回过期数据

 private boolean tryLock(String key){
        //尝试获得互斥锁
        Boolean flag=stringRedisTemplate.opsForValue().setIfAbsent("key","1",LOCK_SHOP_TTL,TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);//通过工具类将其转化为基本类型
    }
    private void unlock(String key){
        //删除锁
        stringRedisTemplate.delete("key");
    }

实现互斥锁相关的方法

//线程池,有10个线程
    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);

创建线程池

public Shop queryWithLogicalExpire(Long id) {
        //实现逻辑过期,解决缓存击穿(不存在缓存穿透问题)
        String key=CACHE_SHOP_KEY+id;
        String lockKey=LOCK_SHOP_KEY+id;
        //1.从redis查询商铺缓存
        String shopJson=stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if(StrUtil.isBlank(shopJson)){
            //3.不存在,返回null
            return null;
        }
        //4.存在,判断是否过期
        //将Json反序列化成RedisDate对象
        RedisData redisData=JSONUtil.toBean(shopJson,RedisData.class);
        Shop shop=JSONUtil.toBean((JSONObject)redisData.getData(),Shop.class);
        //5.过期
        if(LocalDateTime.now().isAfter(redisData.getExpireTime())){
            //6.缓存重建
            //6.1.获取互斥锁
            boolean isLock=tryLock(lockKey);
            //6.2.获取成功
            if(isLock){
                //开启新线程
                CACHE_REBUILD_EXECUTOR.submit(()->{
                    try {
                        saveShopToRedis(id, 20L);
                    }catch(Exception e){
                        throw new RuntimeException(e);
                    }
                    finally {
                        //释放锁
                        unlock(lockKey);
                    }
                });
            }
            //6.3.获取失败
            return shop;
        }
        return shop;
    }

逻辑删除相关方法

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