缓存击穿的逻辑解决方法

缓存击穿是指部分热点key缓存失效的情况,我的解决办法是给缓存加上逻辑上的过期时间,如果到了过期时间则第一个拿到锁的人就会进行缓存的重构,该过程是新开一个独立的线程进行操作,其他人则直接返回旧数据,并且默认情况下redis缓存中是有热点信息的,所以不需要进行非空的判断,我们加的锁是在redis中进行的唯一key会返回操作成功或者失败的原理进行操作的

  private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    /**
     * 缓存击穿逻辑过期
     * @param prefix
     * @param lockPrefix
     * @param id
     * @param type
     * @param dbFallback
     * @param time
     * @param unit
     * @return
     * @param <R>
     * @param <ID>
     */
    //用逻辑过期解决热点问题默认是存在redis中的
    public  <R,ID> R queryWithLogicalExpire(String prefix,String lockPrefix,ID id,Class<R> type, Function<ID,R> dbFallback,Long time, TimeUnit unit ) {
        String key = prefix+id;
        //从redis中查询数据
        String json = stringRedisTemplate.opsForValue().get(key);
        //没有的话直接结束,因为热点数据是自己添加的,默认是有的
        if (StrUtil.isBlank(json)) {
            return null ;
        }
        //4.命中数据后,需要将json数据反序列化
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        JSONObject jsonObject = (JSONObject) redisData.getData();
        R r = JSONUtil.toBean(jsonObject, type);
        LocalDateTime expireTime = redisData.getExpireTime();

        //5判断商品是否过期
        if(expireTime.isAfter(LocalDateTime.now())) {
            //5.1未过期,直接返回数据
            return r;
        }
        //5.2已过期,需要缓存重建
        //6缓存重建
        String  lockKey = lockPrefix+id;
        //6.1获取互斥锁
        boolean isLock = tryLock(lockKey);
        //6.2获取锁是否成功
        if (isLock) {
            //6.3成功,需要开启独立线程,进行缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() ->{
                //重建缓存
                try {
                    //查询数据库
                    R r1 = dbFallback.apply(id);
                    //存入redis
                    this.setWithLogicExpire(key, r1, time, unit);
                }
                catch (Exception e){
                    throw  new RuntimeException();
                }
                finally {
                    //释放锁
                    unlock(lockKey);
                }



            });
        }
        //6.4失败了,缓存过期的商品信息

        return r;
    }

    //做缓存击穿后,进行锁的互斥
    //获取锁
    private boolean tryLock(String lock) {
        Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(lock, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(b);
    }

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

<think>嗯,用户想了解如何有效防止缓存击穿缓存穿透和缓存雪崩的问题。我之前看过几个相关的引用资料,需要先回忆一下这些概念的区别和对应的解决方案。首先,缓存击穿是指热点数据过期后,大量请求直接打到数据库,可能导致数据库压力过大。缓存穿透是查询不存在的数据,导致绕过缓存频繁访问数据库。缓存雪崩则是大量缓存同时失效,引发数据库请求激增。 根据引用[3],缓存击穿解决方案包括互斥锁和逻辑过期。互斥锁可以用分布式锁,确保只有一个线程去重建缓存,其他线程等待。而逻辑过期则是给缓存数据设置一个逻辑过期时间,即使物理时间过期,依然使用旧数据,同时异步更新。引用[4]提到缓存穿透可以用布隆过滤器或者缓存空值,布隆过滤器能快速判断数据是否存在,避免无效查询。缓存空值即使数据库没有,也缓存一个空值,并设置较短的过期时间,减少数据库压力。 缓存雪崩的解决方案在引用[1]和[2]里有提到,比如设置随机过期时间,避免同时失效;使用多级缓存架构,比如nginx+redis+其他缓存,提高可用性;还有集群部署和限流降级措施。需要把这些方法整理清楚,分门别类地回答用户的问题,并且结合示例代码会更直观。比如用Java实现互斥锁,或者用Redis的SETNX命令。另外,用户可能需要最佳实践,比如综合使用多种策略,根据场景选择合适的方法。还要注意区分这三个问题的不同点,避免混淆解决方案。</think>### 如何有效防止缓存击穿缓存穿透与缓存雪崩 #### 一、缓存击穿 **问题描述**:热点数据过期时,大量并发请求直接穿透缓存访问数据库,导致数据库压力骤增[^3]。 **解决方案**: 1. **互斥锁(分布式锁)** 使用分布式锁(如Redis的`SETNX`命令),仅允许一个线程重建缓存,其他线程等待后重试缓存读取。 ```java public String getData(String key) { String data = redis.get(key); if (data == null) { if (redis.setnx(key + "_lock", "1")) { // 获取锁 data = db.query(key); // 重建缓存 redis.setex(key, 300, data); // 设置缓存 redis.del(key + "_lock"); // 释放锁 } else { Thread.sleep(100); // 等待后重试 return getData(key); } } return data; } ``` 2. **逻辑过期** 数据物理永不过期,但存储逻辑过期时间。当发现过期时,异步更新缓存。 ```java class CacheData { String value; long expireTime; // 逻辑过期时间 } ``` --- #### 二、缓存穿透 **问题描述**:请求不存在的数据,缓存和数据库均无记录,导致频繁无效查询[^4]。 **解决方案**: 1. **布隆过滤器(Bloom Filter)** 在缓存层前添加布隆过滤器,快速判断数据是否存在。若过滤器标记不存在,直接拦截请求。 ```java if (!bloomFilter.mightContain(key)) { return null; // 直接返回,不查缓存和数据库 } ``` 2. **缓存空值** 对查询结果为空的请求,缓存空值并设置短过期时间(如5分钟),避免重复穿透[^4]。 ```java if (dbResult == null) { redis.setex(key, 300, "NULL"); // 缓存空值 } ``` --- #### 三、缓存雪崩 **问题描述**:大量缓存同时失效,导致所有请求涌向数据库[^1]。 **解决方案**: 1. **随机过期时间** 为缓存设置基础过期时间+随机偏移值(如`基础300秒 + 随机0~120秒`),避免同时失效。 ```java int expire = 300 + new Random().nextInt(120); redis.setex(key, expire, data); ``` 2. **多级缓存架构** 采用分层缓存(如Nginx本地缓存 + Redis集群 + 数据库),提升系统容错能力[^2]。 3. **高可用与限流** - 部署Redis集群(主从+哨兵) - 使用熔断机制(如Hystrix)或限流工具(如Sentinel)保护数据库。 --- #### 最佳实践总结 | 问题 | 核心方案 | 适用场景 | |------------|-----------------------------------|---------------------------| | 缓存击穿 | 互斥锁、逻辑过期 | 热点数据高频访问 | | 缓存穿透 | 布隆过滤器、空值缓存 | 恶意请求或无效ID查询 | | 缓存雪崩 | 随机过期时间、多级缓存 | 大规模缓存集中失效 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值