缓存击穿

概念

缓存击穿问题也叫热点key问题,指的是一个被高并发访问并且缓存重建业务较为复杂的key突然失效了,大量的请求会到达数据库给数据库带来巨大的冲击。

常见解决方法有两种:互斥锁,逻辑过期

优缺点 :

基于互斥锁的业务代码改造

java 复制代码
ublic Shop queryWithMutex(Long id) {
        //先从redis中查询缓存
        try {
            String object = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY);
            //如果有直接返回
            if (StrUtil.isNotBlank(object)) {
                //转成Java对象
                Shop bean = JSONUtil.toBean(object, Shop.class);
                return bean;
            }
            //判断命中的是否是空值
            if (object != null) {
                return null;
            }
            //实现缓存重建
            //尝试获取锁
            boolean trylock = trylock(LOCK_SHOP_KEY);
            if (!trylock) {
                //如果获取锁失败,休眠并重试
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            //如果没有,查询数据库
            Shop shop = this.getById(id);
            //如果数据库中没有,报错
            if (shop == null) {
                //return Result.fail("没有该店铺");
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //如果有,写入redis
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);
            return shop;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭互斥锁
            unlock(LOCK_SHOP_KEY);
        }
    }



//获取锁和释放锁 (基于redis的setNX实现)

 private boolean trylock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

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

基于逻辑过期的业务代码改造

我们需要新建一个对象来封装我们的店铺信息和逻辑过期时间,所以从redis中查询到数据后需要手动反序列化程Java对象获取我们的逻辑过期时间和店铺信息。

java 复制代码
  private static final ExecutorService CACHE_REBUILD_POOL = Executors.newFixedThreadPool(10);



public Shop queryWithLogicExpire(Long id) {
        //先从redis中查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY);
        //如果有直接返回
        if (StrUtil.isBlank(shopJson)) {
            //如果不存在,直接返回
            return null;
        }
        //命中,需要把json反序列化转成java对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        JSONObject shop = (JSONObject) redisData.getData();
        Shop shopBean = JSONUtil.toBean(shopJson, Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())) {
             //未过期,返回旧数据
            return shopBean;
        }
        //过期,需要缓存重建
        //获取互斥锁
        boolean islock = trylock(LOCK_SHOP_KEY + id);
        if (islock) {
            //如果获取成功,开启线程实现缓存重建
            //此处应该再次判断逻辑时间是否过期
            try {

                CACHE_REBUILD_POOL.submit(() -> {
                    this.saveShop2Redis(id,20l);

            });
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                //释放锁
                unlock(LOCK_SHOP_KEY + id);
            }
        }
        //返回旧数据
        return shopBean;


    }


//缓存重建
  public void saveShop2Redis(Long id, Long expierSecond) {
        //查询店铺信息
        Shop shopById = getById(id);
        //封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shopById);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expierSecond));
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY +id + shopById.getId(), JSONUtil.toJsonStr(redisData), 30, TimeUnit.MINUTES);
    }



//获取锁
 private boolean trylock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    //释放锁
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
相关推荐
小白学大数据1 小时前
基于Scrapy-Redis的分布式景点数据爬取与热力图生成
javascript·redis·分布式·scrapy
Kookoos1 小时前
Redis + ABP vNext 构建分布式高可用缓存架构
redis·分布式·缓存·架构·c#·.net
菜鸟茜4 小时前
从银行排队到零钱支付:用“钱包经济学”重构Java缓存认知
缓存
dddaidai12315 小时前
分布式ID和分布式锁
redis·分布式·mysql·zookeeper·etcd
爱刘温柔的小猪19 小时前
Redis+Caffeine构造多级缓存
redis·spring·缓存
hello1114-19 小时前
Redis学习打卡-Day2-缓存更新策略、主动更新策略、缓存穿透、缓存雪崩、缓存击穿
java·redis·学习·缓存·javaweb
{⌐■_■}1 天前
【redis】redis常见数据结构及其底层,redis单线程读写效率高于多线程的理解,
数据结构·数据库·redis
码农飞哥1 天前
互联网大厂Java求职面试实战:Spring Boot到微服务的技术问答解析
java·spring boot·缓存·面试·消息队列·技术栈·microservices
HumoChen991 天前
jedis+redis pipeline诡异的链接损坏、数据读取异常问题解决
redis·pipeline·jedis
CircleMouse1 天前
springboot如何通过提供的注解方式来操作Redis
java·spring boot·redis·spring·mybatis