缓存三剑客解决方案

发布于:2025-07-14 ⋅ 阅读:(14) ⋅ 点赞:(0)

缓存三剑客解决方案

1.缓存雪崩

定义:大量缓存数据在同一时间点集体失效,导致所有请求直接穿透到数据库,引发数据库瞬时高负载甚至崩溃。

解决方案:设置过期随机值,避免大量缓存同时失效

// 缓存雪崩防护:随机过期时间 + 双层缓存
    
// 设置随机过期时间(基础时间 + 随机偏移)
    Random random = new Random();
    long expire = baseExpire + random.nextInt(5 * 60 * 1000); // 基础5分钟 + 随机5分钟内
    data = loader.load();
    setCache(key, data, expire);
    setCache(backupKey, data, expire * 2); // 备份缓存过期时间更长
    return data;
}

2. 缓存击穿解决方案

定义:某个热点Key突然失效(如过期或被删除),同时有大量并发请求访问该Key,导致请求全部穿透到数据库。

方案1:互斥锁(分布式锁)

在这里插入图片描述

  @Nullable
    // todo 3、缓存击穿 -> 互斥锁:只能由一个线程进行缓存构建,其他线程等待,吞吐量较低
    private Shop huchi(Long id) {
        String shopJsonStr = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
        if (StrUtil.isNotBlank(shopJsonStr)) {
            return JSONUtil.toBean(shopJsonStr, Shop.class);
        }
        // 未命中获取锁
        String tryLockKey = "cache:shop:lock:" + id;
        Shop shop = null;
        try {
            boolean tryLock = getLock(tryLockKey);
            // 未命中:不断休眠直至获取成功
            while (!tryLock) {
                Thread.sleep(50);
                tryLock = getLock(tryLockKey);
            }
            // 获取互斥锁,进行缓存的构建
            shop = getById(id);
            if (shop == null) {
                // 数据库中也不存在时候,进行空字符串缓存
                stringRedisTemplate.opsForValue().set("cache:shop:" + id, "", 2, TimeUnit.MINUTES);
                return null;
            }
            stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop), 2, TimeUnit.MINUTES);

        } catch (Exception e) {
            e.getStackTrace();
        } finally {
            unLock(tryLockKey);
        }

        return shop;
    }

方案2:逻辑过期(适合高并发读场景)

在这里插入图片描述

@Nullable
    // todo 3、缓存击穿 -> 逻辑过期:通过设置逻辑过期时间,然后判断是否过期来确定是否进行缓存更新
    private Shop exLogical(Long id) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        String shopJsonStr = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
        // 如果不存在那就是一定不存在
        if (StrUtil.isBlank(shopJsonStr)) {
            return null;
        }
        //
        RedisDate redisDate = JSONUtil.toBean(shopJsonStr, RedisDate.class);
        Shop shop = JSONUtil.toBean((JSONObject) redisDate.getObject(), Shop.class);
        // 未逻辑过期
        if (redisDate.getEx().isAfter(LocalDateTime.now())) {
            return shop;
        }
        // 逻辑过期
        //  缓存重建
        String tryLockKey = "cache:shop:lock:" + id;
        boolean tryLock = getLock(tryLockKey);
        if (tryLock) {
            // 开启独立的线程去独立的进行缓存
            executorService.submit(() -> {
                try {
                    this.saveShopRedis(id, 20L);
                } finally {
                    unLock(tryLockKey);
                }
            });
        }
        return shop;
    }

    // 手动设置逻辑过期时间
    private void saveShopRedis(Long id, Long ex) {
        Shop shop = getById(id);
        RedisDate redisDate = new RedisDate();
        redisDate.setEx(LocalDateTime.now().plusSeconds(ex));
        redisDate.setObject(shop);
        stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(redisDate));
    }

3. 缓存穿透解决方案

定义:查询数据库中根本不存在的数据(如非法ID或恶意攻击),导致请求绕过缓存直接访问数据库。

方案1:缓存空对象

@Nullable
    //  todo 1、解决缓存穿透问题
    private Shop chaungtou(Long id) {
        // 缓存穿透解决方案 -> 缓存""空字符冲
        String shopJsonStr = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
        if (StrUtil.isNotBlank(shopJsonStr)) {
            return JSONUtil.toBean(shopJsonStr, Shop.class);
        }
        // shopJsonStr == "":代表用户访问的是一个数据库中不存在的数据
        if (shopJsonStr != null) {
            // 店铺不存在
            return null;
        }
        Shop shop = getById(id);
        if (shop == null) {
            // 数据库中也不存在时候,进行空字符串缓存
            stringRedisTemplate.opsForValue().set("cache:shop:" + id, "", 2, TimeUnit.MINUTES);
            return null;
        }
        stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop), 2, TimeUnit.MINUTES);
        return shop;
    }

方案2:布隆过滤器


网站公告

今日签到

点亮在社区的每一天
去签到