本篇文章内容包括:
- 常规思路
- 潜在问题
- 缓存击穿
- 什么是缓存击穿
- 缓存击穿的原因
- 缓存击穿的解决方案
- 使用分布式锁
常规思路
在类似于电商系统中,会存在这样一种情况,比如某个冷门的商品,平时没有多少人去访问,然后公司将这些性价比高的冷门商品进行挖掘出来
然后将这些商品去交给热门的大主播来进行带货,大家看了主播的讲解后觉得东西确实很好,导致一瞬间的流程激增。
又比如某个顶流的明星突然宣布了爆炸性的新闻,比如结婚了、离婚了、劈腿了等等,大家会同一时间去查看类似的新闻,系统的流量同样会激增,前几年微博就是类似在这种情况下宕机了。
在这种情况下,访问数据库时就会造成的数据库的压力过大,很可能会直接将数据库宕机掉
有人说了,这时加个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 欢迎大家与我交流!