首页 > 数据库 >Redis 缓存击穿问题

Redis 缓存击穿问题

时间:2022-12-06 11:45:18浏览次数:44  
标签:shop Shop 缓存 return Redis 击穿 key id

解决方案一:互斥锁

假设一个热门产品的缓存时间到期了,那么将会有大量的请求查询不到缓存,就只能去查询数据库然后再把数据添加到缓存中。但是如果在缓存时间到期的瞬间有很多个请求都来查询这个热门产品,因为缓存当中查询不到数据,导致他们都无法得到数据,只能够去查询数据库,这样便会造成数据库的压力过大,甚至可能导致宕机。

实现流程如下:

 

互斥锁实现代码如下:

//首先编写一个获得锁的方法
private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

  

//然后再写一个释放锁的方法
private void unlock(String key) {
    stringRedisTemplate.delete(key);
}

  

//​ 接下来编写业务方法,查询一个热门产品
    @Override
    public Result queryById(Long id) {
        //缓存穿透
        //Shop shop = queryWithPassThrough(id);
        //互斥锁解决缓存击穿
        Shop shop = queryWithMutex(id);
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        //返回
        return Result.ok(shop);
    }

  

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;
        }
        if (shopJson != null) {
            return null;
        }
        //4.实现缓存重建
        //4.1获取互斥锁
        String lockKey = "lock:shop:" + id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            //4.2判断是否获取成功
            if (!isLock) {
                //4.3失败,则休眠并重试
                Thread.sleep(50);
                //递归
                return queryWithMutex(id);
            }
            //4.4成功,根据id查询数据库
            shop = getById(id);
            //5.不存在,返回错误
            if (shop == null) {
                //缓存击穿问题
                //将空值写入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 (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁
            unlock(lockKey);
        }
        //8.返回
        return shop;
    }

  

解决方案二:逻辑过期

 逻辑过期方案:用户查询某个热门产品信息,如果缓存未命中(即信息为空),则直接返回空,不去查询数据库。如果缓存信息命中,则判断是否逻辑过期,未过期返回缓存信息,过期则重建缓存,尝试获得互斥锁,获取失败则直接返回已过期缓存数据,获取成功则开启独立线程去重构缓存然后直接返回旧的缓存信息,重构完成之后就释放互斥锁。

​ 这样看来,只要命中了缓存,无论是否过期,是否获得锁看,都返回同一个缓存信息

实现流程如下:

 

 

逻辑过期实现代码如下:

//查询某个热门产品信息的方法
@Override
    public Result queryById(Long id) {
        //缓存穿透
        //Shop shop = queryWithPassThrough(id);
        //互斥锁解决缓存击穿
        //Shop shop = queryWithMutex(id);
        //逻辑过期解决缓存击穿
        Shop shop = queryWithLogicalExpire(id);
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        //返回
        return Result.ok(shop);
    }

  

//这里解决 缓存击穿 问题的主要业务方法写在了 queryWithLogicalExpire(id) 中,完整代码如下
 //逻辑过期
    public Shop queryWithLogicalExpire(Long id) {
        String key = CACHE_SHOP_KEY + id;
        //1.从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isBlank(shopJson)) {
            //3.未命中
            return null;
        }
        //4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        Shop shop = (Shop) redisData.getData();
        LocalDateTime expireTime = redisData.getExpireTime();
        //5.判断是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            //5.1还未过期
            return shop;
        }
        //5.2已经过期,需要缓存重建
        //6.缓存重建
        //6.1获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        //6.2判断是否获取锁成功
        if (isLock) {
            // 6.3成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    //重建缓存
                    this.saveShop2Redis(id, 20L);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    unlock(lockKey);
                }
            });
        }
        //6.4返回过期的店铺信息
        //7.返回
        return shop;
    }

  

//由于线程获得锁之后要开启独立线程去重构缓存信息,那么我们最好提前声明定义一个线程池
//线程池
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

  

//除了线程池外,我们还需要 获得锁 和 释放锁 的方法,具体代码如下
   //获得锁
    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

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

  

 

原文链接:
https://blog.csdn.net/Decade_Faiz/article/details/126790237
https://blog.csdn.net/Decade_Faiz/article/details/127056332

流程图参考:https://www.bilibili.com/video/BV1cr4y1671t?p=44&vd_source=41a91db5b456307bec38f0f9b06054e4

标签:shop,Shop,缓存,return,Redis,击穿,key,id
From: https://www.cnblogs.com/xjwly/p/16954703.html

相关文章

  • 字节二面,居然让我写一个 LFU 缓存策略算法,懵了!
    LRU全称"LeastRecentlyUsed",最近最少使用策略,判断最近被使用的时间,距离目前最远的数据优先被淘汰,作为一种根据访问时间来更改链表顺序从而实现缓存淘汰的算法,它是redis......
  • 微信小程序写入缓存再页面跳转,部分机型异常处理
    先看下发现异常的代码:uni.setStorage({ key:"tmp_registerPageInfo", data:_registerPageInfo})uni.navigateTo({ url:"/attestationPackage/pages/bankCard/mai......
  • Redis的三种模式
    一、Redis模式Redis有三种模式:分别是主从同步/复制、哨兵模式、Cluster主从复制:主从复制是高可用Redis的基础,哨兵和群集都是在主从复制基础上实现高可用的。主从复制主要......
  • 缓存行Cache Line
    CacheLine:顾名思义叫做缓存行缓存行越大,局部空间效率越高,读取时间越慢!缓存行越小,局部空间效率越低,读取时间越快!众所周知,计算机将数据从主存读入Cache时,是把要读取......
  • 缓存
    为什么会用到缓存?不同设备的速度不同,磁盘,内存,网络在什么地方用缓存?用户不会修改的:状态的数字与名字的对应,前端缓存地址码->国家,省市区的名字,nginx缓存,key:api+参数,va......
  • 用Java实现分布式缓存(1)——缓存淘汰
    本文代码https://github.com/weloe/Java-Distributed-Cache/tree/main/src/main/java/com/weloe/cache/outstrategyhttps://github.com/weloe/Java-Distributed-Cache/tr......
  • 高速缓存的工作原理
    Cache的基本原理我们先来看一个简单的cache,处理器每次请求一个字,并且每个块由一个单独的字组成。下图展示了该简单cache在请求数据项(该数据项初始不在cache中)前后的状态。......
  • #yyds干货盘点#前端keepalive缓存清理
    说到​​Vue​​​缓存,我们肯定首先选择官方提供的缓存方案​​keep-alive​​内置组件来实现。​​keep-alive​​组件提供给我们缓存组件的能力,可以完整的保存当前组......
  • redis 相关
    Redis提供了丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。后面又支持了四种数据类型: BitMap(2.2版新增)、HyperLogLog(2.8版新增)、......
  • Redis分布式锁及分区
    redis分区的方法redis实现的分布式锁RedLock算法,分布式锁,即在多个master上获取同一个锁1.inordertogetthelock,theclientgetthecurrentmstime2.顺序对n个实......