缓存击穿

概念

缓存击穿问题也叫热点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);
    }
相关推荐
独行侠影a14 小时前
APScheduler+Redis 分布式定时任务:解决多实例任务重复执行
数据库·redis·分布式
梅孔立15 小时前
两种免费的翻译API调用方式详解 - Edge官方API与个人缓存服务
前端·缓存·edge
gwf21616 小时前
SSD读写速度深度解析:顺序读写vs随机读写、IOPS、延迟,你的硬盘性能到底怎么看?
git·嵌入式硬件·缓存·github·智能硬件
番茄炒鸡蛋加糖18 小时前
缓存三大问题
缓存
不在逃避q19 小时前
Golang笔记之Redis
redis·笔记·golang
江晓鱼未暖1 天前
十七、Redis 核心原理与架构详解
大数据·数据库·数据仓库·redis·缓存·架构
红莲凪是1 天前
Redis有哪些部署方案?了解哨兵机制吗?
java·redis·eureka
小罗水1 天前
第13章 Redis 缓存、幂等锁与任务状态
数据库·redis·缓存
热心市民lcj1 天前
Spring Boot 整合 Caffeine 本地缓存实战
spring boot·后端·缓存
zx1154501 天前
【热点 Key 探测与加长缓存 TTL 实战】
缓存