缓存击穿

概念

缓存击穿问题也叫热点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);
    }
相关推荐
煎饼小狗4 小时前
Redis五大基本类型——Zset有序集合命令详解(命令用法详解+思维导图详解)
数据库·redis·缓存
秋意钟6 小时前
缓存雪崩、缓存穿透【Redis】
redis
简 洁 冬冬6 小时前
046 购物车
redis·购物车
雯0609~6 小时前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
soulteary6 小时前
突破内存限制:Mac Mini M2 服务器化实践指南
运维·服务器·redis·macos·arm·pika
wkj0018 小时前
php操作redis
开发语言·redis·php
菠萝咕噜肉i8 小时前
超详细:Redis分布式锁
数据库·redis·分布式·缓存·分布式锁
登云时刻9 小时前
Kubernetes集群外连接redis集群和使用redis-shake工具迁移数据(二)
redis·容器·kubernetes
只因在人海中多看了你一眼11 小时前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
Dlwyz12 小时前
redis-击穿、穿透、雪崩
数据库·redis·缓存