【技术精华】如何解决缓存击穿?

本篇文章内容包括:

  • 常规思路
  • 潜在问题
  • 缓存击穿
  • 什么是缓存击穿
  • 缓存击穿的原因
  • 缓存击穿的解决方案
  • 使用分布式锁

常规思路

在类似于电商系统中,会存在这样一种情况,比如某个冷门的商品,平时没有多少人去访问,然后公司将这些性价比高的冷门商品进行挖掘出来

然后将这些商品去交给热门的大主播来进行带货,大家看了主播的讲解后觉得东西确实很好,导致一瞬间的流程激增。

又比如某个顶流的明星突然宣布了爆炸性的新闻,比如结婚了、离婚了、劈腿了等等,大家会同一时间去查看类似的新闻,系统的流量同样会激增,前几年微博就是类似在这种情况下宕机了。

在这种情况下,访问数据库时就会造成的数据库的压力过大,很可能会直接将数据库宕机掉

有人说了,这时加个Redis缓存来缓解数据库的压力不就好了吗?既然这样,那就模拟一下写一段伪代码:

java 复制代码
public String getData(String id){
    RedisTemplate<String,String> redisTemplate = redisCache.getInstance();
    String cachedValue = redisTemplate.opsForValue().get(id);
    if (StringUtil.isEmpty(cachedValue)) {
        Program program = programMapper.selectById(id);
        if (Objects.nonNull(program)) {
            redisTemplate.opsForValue().set(id,JSON.toJSONString(program));
            cachedValue = JSON.toJSONString(program);
        }
    }
    return cachedValue;
};

首先从redis中查看是否存在,如果不存在则从数据库中查询,接着将查询出的数据放在redis中,最后将数据返回

不少人就是直接这么用的,到这里先不急着往下看,我们可以先仔细想一想,这么写会不会存在问题?

潜在问题

这种写法,在小公司或者并发量不高的情况下没有什么问题,但是在海量的并发场景下就会存在很严重的问题!我们再好好分析这段代码

java 复制代码
public String getData(String id){
    RedisTemplate<String,String> redisTemplate = redisCache.getInstance();
    String cachedValue = redisTemplate.opsForValue().get(id);
    
    //有一种极大的可能性,就是大量的请求都集中在这里判断redis中都是不存在的

    if (StringUtil.isEmpty(cachedValue)) {

        //大量的请求很可能会执行到这里
        
        Program program = programMapper.selectById(id);
        if (Objects.nonNull(program)) {
            redisTemplate.opsForValue().set(id,JSON.toJSONString(program));
            cachedValue = JSON.toJSONString(program);
        }
    }
    return cachedValue;
};

因为都是并发的请求,当一瞬间大量的请求过来时,很有可能大量的请求都是在 查询redis中的数据判断是不存在的,那么还是最终都去查询数据库了,虽然第一次请求从数据库中查询,然后放入到redis中,但其他的请求已经执行了redis判断的阶段,也已经去查询数据库了

也就是说这时加缓存完全没啥用了,最终导致的结果是虽然缓存中有数据,但还是大量的请求执行到了数据库层面,这就是常说的 缓存击穿!

缓存击穿

在微服务架构中,Redis等缓存系统被广泛用于提高系统的响应速度和减轻数据库的压力。通过将热点数据存储在缓存中,系统可以直接从缓存中获取数据,而无需每次都查询数据库,从而大大提高了系统的性能。然而,在使用缓存的过程中,也会遇到一些问题,其中之一就是缓存击穿问题。

什么是缓存击穿

缓存击穿是指当缓存中没有某个热点数据的缓存时(一种情况是缓存时间到期,另一种是大量请求集中在程序某一处判定为缓存不能存在),而此时恰好有大量的并发请求请求这个数据,这些请求都会直接打到数据库上,造成数据库短时间内承受大量请求而崩掉。

缓存击穿的原因

  • 缓存失效:当缓存中的数据因为过期时间到达而被删除,而新的缓存数据还没有被加载进来时,如果此时有大量的请求到来,这些请求都会直接打到数据库上,造成缓存击穿。
  • 缓存未命中:由于某些原因(如数据更新、缓存被误删等),缓存中不存在某个数据的缓存,当大量请求同时访问这个数据时,它们都会穿透缓存直接访问数据库。
  • 对于第一次请求:当第一次请求时,会判断缓存中没有存在这个数据,请求就会直接落到数据库上,当瞬间有大量的请求时,很有可能都会判断缓存中没有数据,直接全都打到了数据库层面上(开头举例就是此情况)

缓存击穿的解决方案

  • 设置热点数据永远不过期: 这是一种简单直接的解决方案,但是会导致缓存中的数据无法实时更新,可能引发数据不一致的问题
  • 分布式锁: 当缓存失效时,不是立即去加载数据库数据,而是先使用分布式锁去获取加载数据的权限,当获取到权限后,再去加载数据到缓存。这样,在第一个请求去加载数据时,其他并发请求则需要等待,第一个请求将数据加载到缓存后,直接释放分布式锁,此时其他请求就能够从缓存中获取数据,而不需要再去查询数据库

解决方案的分析

  • 第一种方案数据不过期肯定是不行的,就算只存放热点数据,但是也会随着数据量越来越大的情况下,redis的承受压力也会越来越大
  • 第二种使用分布式锁让请求进行串行化,确实可以解决缓存击穿的问题,我们来写一段代码实现使用分布式锁的方案

使用分布式锁

java 复制代码
public String getDataV2(String id){
    RedisTemplate<String,String> redisTemplate = redisCache.getInstance();
    String cachedValue = redisTemplate.opsForValue().get(id);
    if (StringUtil.isEmpty(cachedValue)) {
        //分布式锁
        RLock lock = serviceLockTool.getLock(LockType.Reentrant, id);
        lock.lock();
        try {
            Program program = programMapper.selectById(id);
            if (Objects.nonNull(program)) {
                redisTemplate.opsForValue().set(id,JSON.toJSONString(program));
                cachedValue = JSON.toJSONString(program);
            }
        } finally {
            lock.unlock();
        }
    }
    return cachedValue;
}

使用分布式锁的方式,就只能允许同一时刻只有一个请求来从数据库中查询数据,然后设置到缓存中,而其他的请求都要等待获得锁的请求执行完毕,这样大量的请求就不会同时到数据库层面

大家仔细想想,这段代码还存在着什么问题?有什么可以优化的地方吗?欢迎评论区交流!



🌸🌸🌸 完结撒花🌸🌸🌸

博主WX:g2279605572 欢迎大家与我交流!

相关推荐
刘大猫268 分钟前
Arthas profiler(使用async-profiler对应用采样,生成火焰图)
java·人工智能·后端
滴水可藏海22 分钟前
EasyExcel系列:读取空数据行的问题
java
王小二_Leon30 分钟前
JAVA中正则表达式的入门与使用
java·正则表达式
骑牛小道士44 分钟前
java基础 运算符
java
pwzs1 小时前
缓存不只是加速器:深入理解 Redis 的底层机制
数据库·redis·缓存
A尘埃1 小时前
电商中的购物车(redis的hash类型操作)
数据库·redis·哈希算法
EasyControl移动设备管理1 小时前
MDM功能演示:远程锁定与数据擦除,保障企业移动设备安全
安全
安科瑞刘鸿鹏1 小时前
智能配电保护:公共建筑安全的新 “防火墙”
运维·网络·物联网·算法·安全·能源
晴天毕设工作室1 小时前
计算机毕业设计指南
java·开发语言·python·计算机网络·课程设计
100分题库小程序1 小时前
湖北建筑安全员C1证考试难度怎么样
经验分享·笔记