redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 什么是缓存
  • 添加Redis缓存
  • 缓存更新策略
  • 缓存穿透
  • 缓存雪崩
  • 缓存击穿
    • 互斥锁解决缓存击穿
    • 逻辑过期解决缓存击穿


什么是缓存

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第1张图片

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第2张图片

添加Redis缓存

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第3张图片

/**
     * 根据id查询商铺信息
     * @param id 商铺id
     * @return 商铺详情数据
     */
    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {


        return Result.ok(shopService.queryById(id));
    }
@Override
    public Result queryById(Long id) {
        String key = CACHE_SHOP_KEY+id;
        //1、从redis中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2、判断是否存在
        if (StrUtil.isNotBlank(shopJson)){
            //3、存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }

        //4、不存在,根据id查询数据库
        Shop shop = getById(id);
        //5、数据库中不存在,返回错误
        if (shop==null){
            return Result.fail("店铺不存在");
        }
        //6、数据库中存在,写进redis并返回
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        return Result.ok(shop);
    }

缓存更新策略

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第4张图片
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第5张图片
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第6张图片
先删除缓存,再更新数据库
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第7张图片
不考虑并发的情况下,不会出现缓存和数据库不一致的问题
并发情况下,就会存在缓存和数据不一致的问题
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第8张图片

先操作数据库,再删缓存
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第9张图片
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第10张图片

无论那种方式,都会存在缓存和数据库不一致的问题

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第11张图片

解决缓存于数据库部不一致的问题https://juejin.cn/post/6844903929281511438

    @Override
    @Transactional
    public Result updateShop(Shop shop) {
        Long id = shop.getId();
        if (id==null){
            return Result.fail("店铺id不能为空");
        }
        //1、更新数据库
        updateById(shop);
        //2、删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+shop.getId());
        return Result.ok();

    }

@Override
    public Result queryById(Long id) {
        String key = CACHE_SHOP_KEY+id;
        //1、从redis中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2、判断是否存在
        if (StrUtil.isNotBlank(shopJson)){
            //3、存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }

        //4、不存在,根据id查询数据库
        Shop shop = getById(id);
        //5、数据库中不存在,返回错误
        if (shop==null){
            return Result.fail("店铺不存在");
        }
        //6、数据库中存在,写进redis并返回
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        return Result.ok(shop);
    }

上面的解决方式是,给redis中的key都设置上时间作为兜底方案,更新数据的时候,先更新数据库再删除缓存,这种方案并不能带来强一致性

缓存穿透

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第12张图片
缓存空对象解决缓存击穿
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第13张图片

@Override
    public Result queryById(Long id) {
        String key = CACHE_SHOP_KEY+id;
        //1、从redis中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2、判断是否存在
        if (StrUtil.isNotBlank(shopJson)){
            //3、存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //todo 判断命中的是否是空值
        if (shopJson==null){
            return Result.fail("店铺信息不存在");
        }
        //4、不存在,根据id查询数据库
        Shop shop = getById(id);
        //5、数据库中不存在,返回错误
        if (shop==null){
            //todo 将控制,写入redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        //6、数据库中存在,写进redis并返回
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        return Result.ok(shop);
    }

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第14张图片

缓存雪崩

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第15张图片

缓存击穿

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第16张图片
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第17张图片
redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第18张图片

互斥锁适合于解决强一致性的场景,对缓存和数据库一致性要求比较高的;如果对缓存和数据库一致性要求不高,而追求高可用的场景,逻辑过期更适合

互斥锁解决缓存击穿

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第19张图片

public Shop queryWithMutex(Long id){
        String key = CACHE_SHOP_KEY+id;
        //1、从redis中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2、判断是否存在
        if (StrUtil.isNotBlank(shopJson)){
            //3、存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //todo 判断命中的是否是空值
        if (shopJson==null){
            return null;
        }
        //4、实现缓存重建
        //4.1、获取互斥锁
        String lockKey = "locl:shop"+id;
        boolean isLock = tryLock(lockKey);
        //4.2、判断是否获取成功
        //4.3、失败,休眠并重试
        Shop shop=null;
        try {
            if (!isLock){
                Thread.sleep(3000);
                return   queryWithMutex(id);

            }
            //4.4、成功,根据id查询数据库
             shop = getById(id);
            //5、数据库中不存在,返回错误
            if (shop==null){
                //todo 将控制,写入redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                return null;
            }
            //6、数据库中存在,写进redis并返回
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);

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

逻辑过期解决缓存击穿

redis解决商户查询缓存以及如何解决缓存穿透、缓存雪崩、缓存击穿_第20张图片

  public Shop queryWithLogcialExpire(Long id){
        String key = CACHE_SHOP_KEY+id;
        //1、从redis中查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2、判断是否存在
        if (StrUtil.isBlank(shopJson)){
            return null;
        }
        // todo 命中
        //4、需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);

        //5、判断是否过期
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())){
            //5.1 未过期,直接返回店铺信息
            return shop;
        }

        //5.2  已过期,需要缓存重建
        //6、缓存重建
        //6.1、获取互斥锁
        String lockKey = LOCK_SHOP_KEY+id;
        //6.2 判断是否获取锁成功
        boolean islock = tryLock(lockKey);
        if (islock){
            // 6.3 成功,开启独立线程,实现缓存重建、
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try{
                    saveShop2redis(id,30L);
                    
                }catch (Exception e){
                    throw new RuntimeException(e);
                }finally {
                    //释放锁
                    unLocak(lockKey);
                }
               
            });
        }
        // 6.4、失败,返回过期的商铺信息
       
        return shop;
    }
  private  void saveShop2redis(Long id,Long expireSeconds) throws InterruptedException {
        //查询店铺数据
        Shop shop = getById(id);
        Thread.sleep(200);
        //封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        //写入redis
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }
@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

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