JavaGuide 官方网站:javaguide.cn
JavaGuide 官方星球:JavaGuide知识星球
预热一般指缓存预热,一般用在高并发系统中,为了提升系统在高并发情况下的稳定性的一种手段。
缓存预热是指在系统启动之前或系统达到高峰期之前,通过预先将常用数据加载到缓存中,以提高缓存命中率和系统性能的过程。缓存预热的目的是尽可能地避免缓存击穿和缓存雪崩,还可以减轻后端存储系统的负载,提高系统的响应速度和吞吐量。
缓存预热的好处有很多,如:
缓存预热的一般做法是在系统启动或系统空闲期间,将常用的数据加载到缓存中,主要做法有以下几种:
在分布式缓存中,我们通常都是使用 Redis,针对 Redis 的预热,有以下几个工具可供使用,帮助我们实现缓存的预热:
在应用程序启动时,可以通过监听应用启动事件,或者在应用的初始化阶段,将需要缓存的数据加载到缓存中。
ApplicationReadyEvent
是 Spring Boot 框架中的一个事件类,它表示应用程序已经准备好接收请求,即应用程序已启动且上下文已刷新。这个事件是在 ApplicationContext
被初始化和刷新,并且应用程序已经准备好处理请求时触发的。
基于ApplicationReadyEvent
,我们可以在应用程序完全启动并处于可用状态后执行一些初始化逻辑。使用 @EventListener
注解或实现 ApplicationListener
接口来监听这个事件。例如,使用 @EventListener
注解:
@EventListener(ApplicationReadyEvent.class)
public void preloadCache() {
// 在应用启动后执行缓存预热逻辑
// ...
}
如果你不想直接监听 ApplicationReadyEvent
,在 SpringBoot 中,也可以通过 CommandLineRunner
和 ApplicationRunner
来实现这个功能。
CommandLineRunner
和 ApplicationRunner
是 Spring Boot 中用于在应用程序启动后执行特定逻辑的接口。这解释听上去就像是专门干这个事儿的。
MyCommandLineRunner.java
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 在应用启动后执行缓存预热逻辑
// ...
}
}
MyApplicationRunner.java
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 在应用启动后执行缓存预热逻辑
// ...
}
}
CommandLineRunner
和 ApplicationRunner
的调用,是在 SpringApplication
的 run
方法中
其实就是 callRunners(context, applicationArguments);
的实现:
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
实现 InitializingBean
接口,并在 afterPropertiesSet
方法中执行缓存预热的逻辑。这样,Spring 在初始化 Bean 时会调用 afterPropertiesSet
方法。
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class CachePreloader implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// 执行缓存预热逻辑
// ...
}
}
类似的,我们还可以使用 @PostConstruct
注解标注一个方法,该方法将在 Bean 的构造函数执行完毕后立即被调用。在这个方法中执行缓存预热的逻辑。
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class CachePreloader {
@PostConstruct
public void preloadCache() {
// 执行缓存预热逻辑
// ...
}
}
在启动过程中预热有一个问题,那就是一旦启动之后,如果需要预热新的数据,或者需要修改数据,就不支持了,那么,在应用的运行过程中,我们也是可以通过定时任务来实现缓存的更新预热的。
我们通常依赖这种方式来确保缓存中的数据是最新的,避免因为业务数据的变化而导致缓存数据过时。
在 Spring 中,想要实现一个定时任务也挺简单的,基于@Scheduled 就可以轻易实现.
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
public void scheduledCachePreload() {
// 执行缓存预热逻辑
// ...
}
也可以依赖 xxl-job 等定时任务实现。
些缓存框架提供了缓存加载器的机制,可以在缓存中不存在数据时,自动调用加载器加载数据到缓存中。这样可以简化缓存预热的逻辑。如 Caffeine 中就有这样的功能:
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class MyCacheService {
private final LoadingCache<String, String> cache;
public MyCacheService() {
this.cache = Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.MINUTES) // 配置自动刷新,1分钟刷新一次
.build(key -> loadDataFromSource(key)); // 使用加载器加载数据
}
public String getValue(String key) {
return cache.get(key);
}
private String loadDataFromSource(String key) {
// 从数据源加载数据的逻辑
// 这里只是一个示例,实际应用中可能是从数据库、外部服务等获取数据
System.out.println("Loading data for key: " + key);
return "Value for " + key;
}
}
在上面的例子中,我们使用 Caffeine.newBuilder().refreshAfterWrite(1, TimeUnit.MINUTES)
配置了缓存的自动刷新机制,即每个缓存项在写入后的 1 分钟内,如果有读请求,Caffeine 会自动触发数据的刷新。
loadDataFromSource
方法是用于加载数据的自定义方法。你可以在这个方法中实现从数据源(例如数据库、外部服务)加载数据的逻辑。
👉推荐:
点击下方卡片进入公众号
回复 「PDF」 即可领取原创PDF技术面试手册
回复 「学习路线」 即可获取4w+字最新版Java学习路线
回复 「开源」 即可获取优质Java开源项目合集
免费分享无套路,有帮助点个赞就好!