什么是缓存雪崩?如何解决缓存雪崩?
缓存雪崩指的是在短时间内,有大量的请求直接查询术后句酷,从而对数据库造成大量的压力,严重情况下可能导致数据库宕机的情况叫做缓存雪崩
我们可以看一下正常的情况下程序执行流程图:
当出现缓存雪崩的时候,流程图如下:
由此可以看出缓存雪崩造成的影响,导致缓存雪崩的主要原因有以下几种:
- 缓存过期时间设置不合理:当大量缓存数据在同一时间失效时,会导致大量请求直接打到数据库或者后端服务
- 缓存服务器故障:如果缓存服务器发生故障,无法体哦共缓存服务,那么所有请求都会直接访问数据库或后端服务
- 缓存数据的热点分布不均:如果某些热门数据集中一部分缓存节点上,当这些节点发生故障或者数据失效的 ,会导致请求直接打到数据库或者后端服务
如何解决缓存雪崩
缓存雪崩的常见解决方法有以下几种:
- 随机生成缓存过期时间:随机生成缓存过期时间,可以避免缓存同时过期,从而让避免缓存雪崩问题
//缓存原本的失效时间
int exTime = 10*60;
//随机数生成类
Random random = new Random();
//缓存设置
jedis.setex(cacheKey,exTime+random.nextInt(1000) ,value);
- 使用多级缓存:可以使用多级缓存架构,将热门数据同时缓存在多个缓存节点上,避免单一节点故障导致请求直接访问数据库或者后端服务,例如可以设计二级换内存(分布式缓存+本地缓存),如图:
- 缓存过期前预加载:在缓存即将过期之前,提前异步加载缓存,避免在缓存失效时大量请求直接打到数据库或者后端服务
- 开启限流或降级功能:当缓存发生雪崩时,采用限流或降级的机制来减轻服务器压力,保证系统可用性
- 实时及监控和预警:通过监控缓存的状态和命中率,及时发现缓存的问题
什么是缓存穿透?如何解决缓存穿透?
缓存穿透是指,当我们查询一个数据库和缓存中都不存在的数据时,由于数据库查询结果为空,出于容错考虑,我们通常不会将这个空结果保存到缓存中。因此,每次对这个数据的请求都会直接查询数据库,而不是缓存。这就导致数据库需要处理额外的查询压力,从而可能降低系统的整体性能
简单来说,缓存穿透就是指数据库查询没有数据,出于容错考虑,不会将结果保存到缓存中,因此每次请求都会去查询数据库
缓存穿透执行流程如下:
其中红色路径代表缓存穿透的执行路径,可以看出缓存穿透会给数据库造成很大压力
如何解决缓存穿透
- 缓存空对象:对于查询结果为null或不存在的数据,也可以将它们以特殊值(如:NULL或特殊符号)进行缓存,并设置较短的过期时间。这样,短时间相同的查询请求就可以直接从缓存中获得响应,避免了对数据库的直接查询
- 布隆过滤器:在请求达到缓存之前,先通过布隆过滤器判断数据可能存在还是一定不存在。对于不存在的数据,可以直接返回;可能存在的则继续查询缓存和数据库。布隆过滤器是一种空间效率极高的概率型数据结构,他会给出“可能存在”或者“一定不存在”的答案
- 开启限流功能:当发现大量连续未命中的请求的时候,可以采用限流策略限制同一时间内向数据库发送的查询请求数量,减轻数据库压力
什么是缓存击穿?如何解决缓存击穿?
缓存击穿是指某个热点缓存,在某一时刻恰好失效了,然后此时刚好有大量的并发请求,此时这些请求会给数据库造成巨大的压力
缓存击穿的执行流程:
缓存击穿主要的原因是热点数据在缓存中失效或被淘汰,并发请求同属访问该数据,导致缓存无法命中
如何解决缓存击穿
- 设置永不过期:对于某些热点缓存,我们可以设置成永不过期,这样就保证缓存的稳定性,但是需要注意在数据更改之后,要及时更新此热点缓存,不然会造成查询结果的误差
- 缓存过期前预加载:在缓存即将过期之前,提前异步加载缓存,避免在缓存失效时大量的请求直接打到数据库或者后端服务
- 使用多级缓存:可以使用多级缓存架构,将热门数据同时缓存在多个缓存节点上,避免单一节点故障导致请求直接访问数据库或者后端服务。例如可以设计多级缓存,也就是使用分布式缓存(Redis)+本地缓存(Caffeine/Guava Cache) ,如下图所示:
- 开启限流或降级功能:当缓存发生雪崩时,采用限流或降级的机制来减轻服务器压力,保证系统可用性
什么是缓存预热?如何实现缓存预热?
缓存与炽热是指在系统启动、高峰期来临之前或者数据变更之后,提前将热门或者需要经常访问的数据加载到缓存中,以提高系统的响应性能和缓存命中率。通过缓存预热,可以避免在实际请求到来的时候出现缓存穿透和缓存击穿的情况,减少对后端存储的直接访问
实现缓存你预热的一般步骤如下:
- 确定热门数据:首先需要确定哪些数据是热门或者经常访问的数据。可以通过系统日志、业务需求、数据统计分析等方式进行评估
- 加载数据到缓存:在系统启动、高峰期来临之前或者数据变更之后,提前将热门数据加载到缓存中。可以通过定时任务、异步加载、批量加载等方式来实现数据加载
- 设置适当的过期时间:根据业务需求和数据的访问频率,设置适当的缓存过期时间。过期时间可以根据不同的数据进行灵活调整,以保证缓存数据的有效性
- 监控和维护:在缓存预热完成后,需要进行监控和维护。可以通过监控缓存命中率、缓存失效率指标来评估
缓存预热的实现
手动初始化:在程序启动阶段或者服务初始化的时候,通过编写代码主动的从数据库加载热点数据,并将其放入缓存中(如:Redis)
//初始化阶段加载热点数据
public void warmUpCache() {
List<HotData> hotDatas = loadHotDataFromDatabase();
for (HotData data : hotDatas) {
string key = buildKey(data.getId());
redisTemplate.opsForValue().set(key, data expirationTime TimeUnit.MINUTES);
}
}
定时任务:使用定时任务定期刷新或者加载数据到缓存中,可以是固定时间间隔,也可以是在数变更后触发
事件驱动:当有新的数据添加到数据库时,触发一个时间来通知缓存系统加载数据
使用框架:某些框架或者中间件提供了缓存预热功能的支持。例如,在Spring Boot项目中,可以通过实现CommandLineRunner或ApplicationRunner接口,在应用启动自动加载数据到缓存
你的代码片段似乎有一些语法错误,我已经为你修正了。在Spring Boot中,一个类可以同时实现CommandLineRunner
和ApplicationRunner
接口,并且可以在run
方法中添加自定义的操作。以下是修正后的代码:
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyRunner implements CommandLineRunner, ApplicationRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("This is CommandLineRunner"); // 实现自定义操作
}
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("This is ApplicationRunner"); // 实现自定义操作
}
}
在这段代码中,MyRunner
类实现了CommandLineRunner
和ApplicationRunner
接口。当Spring Boot应用启动完成后,它会自动执行这两个接口的run
方法。在这两个方法中,你可以添加自定义的操作,比如加载数据到缓存(即缓存预热)等