当前位置: 首页 > news >正文

Redis-缓存穿透击穿雪崩

1. 穿透问题

缓存穿透问题就是查询不存在的数据。在缓存穿透中,先查缓存,缓存没有数据,就会请求到数据库上,导致数据库压力剧增。

解决方法:

  1. 给不存在的key加上空值,防止每次都会请求到数据库。
  2. 布隆过滤器,做一次过滤

1.1 使用缓存空值解决缓存击穿问题

  1. 根据id=1来请求
  2. redis存在数据
    2.1. 存储的是空值{},那么返回null
    2.2. 存储的不是空值,说明存储的是真实的数据库数据
  3. redis不存在数据
  4. 查询数据库
    4.1. 数据库存在数据,那么缓存数据到redis,返回真实的数据
    4.2. 数据库不存在数据,那么缓存空对象 {},设置一个过期时间,返回空

@Component
public class RedisCacheClient {
    private final StringRedisTemplate stringRedisTemplate;

    public RedisCacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private void set(String key, Object value, Long time, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, timeUnit);
    }

    private String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    public <ID, R> R queryWithPassThrough(String keyPrefix, ID id, Class<R> clazz,
                                          Function<ID, R> dbFallBack, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询数据
        String json = get(key);
        // 2.判断数据是否存在
        if (RedisConstants.EMPTY_OBJECT_JSON.equals(json)) {
            return null; //缓存的空对象值
        }
        if (StrUtil.isNotEmpty(json)) {
            return JSONUtil.toBean(json, clazz);
        }
        // 3.不存在,根据id查询数据库
        R r = dbFallBack.apply(id);
        if (r != null) {
            set(key, r, time, unit);
            return r;
        }
        // 4.存储空对象
        set(key, RedisConstants.EMPTY_OBJECT_JSON /*{}*/, RedisConstants.CACHE_NULL_TTL, TimeUnit.SECONDS);
        return null;
    }
}

1.2 使用布隆过滤器做初次判断

对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不存在的数据,布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。

在这里插入图片描述

布隆过滤器就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀。

向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度 进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就 完成了 add 操作。向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个key 不存在。如果都是 1,这并不能说明这个key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会降低。

这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景, 代码维护较为复杂, 但是缓存空间占用很少。


1.2.1 导入pom坐标
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson</artifactId>
  <version>3.13.6</version>
</dependency>
1.2.2 布隆过滤器代码示例
class Main {
    private RedissonClient redissonClient;

    void test() {
        RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("orderList");
        // 1.初始化布隆过滤器:预计元素为100000000L,误判率为3%,根据这两个参数会计算出底层的bit数组大小
        bloomFilter.tryInit(100000000L, 0.03);

        // 2.添加元素到bloomFilter
        bloomFilter.add("ayuan");

        // 3.判断下面的数据是否在布隆过滤器中
        System.out.println(bloomFilter.contains("asheng"));
        System.out.println(bloomFilter.contains("longge"));
        System.out.println(bloomFilter.contains("ayuan"));
    }
}

使用布隆过滤器需要把所有数据提前放入布隆过滤器,并且在增加数据时也要往布隆过滤器里放,布隆过滤器缓存过滤伪代码:
在这里插入图片描述

1.2.3 布隆过滤器实战
class Main {
    @Autowired
    private RedissonClient redissonClient;

    private RBloomFilter<String> bloomFilter;

    @PostConstruct
    void init() {
        // 1.初始化布隆过滤器
        bloomFilter = redissonClient.getBloomFilter("orderList");
        // 初始化布隆过滤器:预计元素为100000000L,误判率为3%,根据这两个参数会计算出底层的bit数组大小
        bloomFilter.tryInit(100000000L, 0.03);
        // 2.加载所有的数据加载到布隆过滤器
       // for (String key : keys) {
       //     bloomFilter.add(key);
       // }
    }

    @Test
    String get(String key) {
        // 3.从布隆过滤器这一级缓存判断key是否存在
        boolean isContains = bloomFilter.contains(key);
        if (!isContains) {
            return "";
        }
        // 4.业务逻辑开发
    }
}

但是布隆过滤器无法删除某一个元素,如果要删除得重新初始化数据

2. 击穿问题

缓存击穿中,请求的 key 对应的是热点数据 ,该数据存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
[图片]


解决方案:

  1. 基于互斥锁(看情况):在缓存过期后,通过设置互斥锁确保只有一个请求去查询数据库并且更新缓存。
  2. 提前预热(推荐):针对热点数据提前预热,并将其入缓存中并设置合理的过期事件,比如:秒杀场景下的数据在秒杀结束前永不过期。
  3. 数据永不过期(不推荐):设置热点数据永不过期或者过期时间比较长。

2.1 基于互斥锁解决缓存击穿问题

@Component
public class RedisCacheClient {
    private final StringRedisTemplate stringRedisTemplate;
    private final RedissonClient redissonClient;

    public RedisCacheClient(StringRedisTemplate stringRedisTemplate, RedissonClient redissonClient) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.redissonClient = redissonClient;
    }

    private void set(String key, Object value, Long time, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, timeUnit);
    }

    private String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    public <ID, R> R query(String keyPrefix, ID id, Class<R> clazz,
                                          Function<ID, R> dbFallBack, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询数据
        String json = get(key);
        // 2.判断数据是否存在
        if (RedisConstants.EMPTY_OBJECT_JSON.equals(json)) {
            return null; //缓存的空对象值
        }
        if (StrUtil.isNotEmpty(json)) {
            return JSONUtil.toBean(json, clazz);
        }

        //加锁,防止缓存击穿问题 -> redis的热点key问题
        RLock redissonClientLock = redissonClient.getLock(RedisConstants.DISTRIBUTED_LOCK + key);
        redissonClientLock.lock(); //加锁
        try {
            //dcl判断锁是否存在了
            json = get(key);
            if (json != null) {
                return queryWithPassThrough(keyPrefix, id, clazz, dbFallBack, time, unit);
            }
            //3. 不存在,根据id查询数据库
            R r = dbFallBack.apply(id);
            if (r != null) {
                set(key, r, time, unit);
                return r;
            }
            // 存储空对象
            set(key, RedisConstants.EMPTY_OBJECT_JSON, RedisConstants.CACHE_NULL_TTL, TimeUnit.SECONDS);
            return null;
        } finally {
            redissonClientLock.unlock();
        }
    }
}

3. 雪崩问题

缓存宕机或者在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。

在这里插入图片描述


解决方式:

  1. 设置随机失效时间(可选):为缓存设置随机的失效时间,例如在固定过期时间的基础上加上一个随机值,这样可以避免大量缓存同时到期,从而减少缓存雪崩的风险。(例如:批量导入数据到redis的时候,如果设置过期时间一致,那么就会数据就会在同一时刻过期删除)。
  2. 多级缓存:设计多级缓存,例如本地缓存+Redis 缓存的二级缓存组合,当 Redis 缓存出现问题时,还可以从本地缓存中获取到部分数据。
  3. Redis集群:采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。比如:Redis Sentinel哨兵集群、Redis Cluster分片集群。
  4. 限流:如果发现读请求太多,可以采用限流的策略。

相关文章:

  • paimon---同步mysql数据到paimon表中
  • 基于Spring Boot的国产动漫网站的设计与实现(LW+源码+讲解)
  • TDengine 数据对接 EXCEL
  • 记一次Spring Boot应用中数据库连接阻塞问题排查过程
  • 重生之我在学Vue--第8天 Vue 3 UI 框架(Element Plus)
  • 深度学习——Diffusion Model学习,扩散模型
  • deepseek使用记录21——游击战略问题
  • python 中用到的文件操作
  • 从运营出发:打造更适配当下营商环境的一对一直播系统源码
  • MySQL(第3周)-database命令
  • Python自动点击器开发教程 - 支持键盘连按和鼠标连点
  • 多线程(二)
  • 蓝桥杯真题0团建dfs+哈希表/邻接表
  • 统计登录系统10秒内连续登录失败超过3次的用户
  • 看 MySQL InnoDB 和 BoltDB 如何写磁盘
  • Vivado IP核之定点数累加Accumulator使用说明
  • vscode接入DeepSeek 免费送2000 万 Tokens 解决DeepSeek无法充值问题
  • 向量数据库的选择与应用:AI工程实践
  • Android Retrofit 框架注解定义与解析模块深度剖析(一)
  • HarmonyOS NEXT开发实战:DevEco AI辅助编程工具(CodeGenie)的使用
  • 如何给喜欢的明星做网站/全网营销
  • 深圳建设工程网/北京seo服务行者
  • 舆情信息网站/今晚赛事比分预测
  • 网站备案完成通知书/网络软文是什么意思
  • 免费永久vps服务器/谷歌关键词优化怎么做
  • 济南市公众号网站建设/搜全网的浏览器