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

本篇文章内容包括:

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

常规思路

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

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

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

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

有人说了,这时加个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 欢迎大家与我交流!

相关推荐
测试界萧萧1 小时前
15:00面试,15:06就出来了,问的问题有点变态。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展
小春学渗透1 小时前
DAY168内网对抗-基石框架篇&单域架构&域内应用控制&成员组成&用户策略&信息收集&环境搭建
网络·安全·架构·内网攻防
摸摸陌陌2 小时前
Redis快速入门
数据库·redis·缓存
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS加油站管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·maven
Hello.Reader3 小时前
Spring Retry 与 Redis WATCH 结合实现高并发环境下的乐观锁
java·redis·spring
纯洁的小魔鬼3 小时前
redis 基础
数据库·redis·命令行
西岭千秋雪_3 小时前
设计模式の单例&工厂&原型模式
java·单例模式·设计模式·简单工厂模式·工厂方法模式·抽象工厂模式·原型模式
fanchael_kui3 小时前
使用elasticsearch-java客户端API生成DSL语句
java·大数据·elasticsearch
m0_748256563 小时前
[CTF夺旗赛] CTFshow Web1-14 详细过程保姆级教程~
java
T.O.P113 小时前
Spring&SpringBoot常用注解
java·spring boot·spring