缓存没预热,翻车了!

JavaGuide 官方网站javaguide.cn
JavaGuide 官方星球
JavaGuide知识星球

预热一般指缓存预热,一般用在高并发系统中,为了提升系统在高并发情况下的稳定性的一种手段。

缓存预热是指在系统启动之前或系统达到高峰期之前,通过预先将常用数据加载到缓存中,以提高缓存命中率和系统性能的过程。缓存预热的目的是尽可能地避免缓存击穿和缓存雪崩,还可以减轻后端存储系统的负载,提高系统的响应速度和吞吐量。

预热的必要性

缓存预热的好处有很多,如:

  1. 减少冷启动影响:当系统重启或新启动时,缓存是空的,这被称为冷启动。冷启动可能导致首次请求处理缓慢,因为数据需要从慢速存储(如数据库)检索。
  2. 提高数据访问速度:通过预先加载常用数据到缓存中,可以确保数据快速可用,从而加快数据访问速度。
  3. 平滑流量峰值:在流量高峰期之前预热缓存可以帮助系统更好地处理高流量,避免在流量激增时出现性能下降。
  4. 保证数据的时效性:定期预热可以保证缓存中的数据是最新的,特别是对于高度依赖于实时数据的系统。
  5. 减少对后端系统的压力:通过缓存预热,可以减少对数据库或其他后端服务的直接查询,从而减轻它们的负载。

预热的方法

缓存预热的一般做法是在系统启动或系统空闲期间,将常用的数据加载到缓存中,主要做法有以下几种:

  • 系统启动时加载:在系统启动时,将常用的数据加载到缓存中,以便后续的访问可以直接从缓存中获取。
  • 定时任务加载:定时执行任务,将常用的数据加载到缓存中,以保持缓存中数据的实时性和准确性。
  • 手动触发加载:在系统达到高峰期之前,手动触发加载常用数据到缓存中,以提高缓存命中率和系统性能。
  • 用时加载:在用户请求到来时,根据用户的访问模式和业务需求,动态地将数据加载到缓存中。
  • 缓存加载器:一些缓存框架提供了缓存加载器的机制,可以在缓存中不存在数据时,自动调用加载器加载数据到缓存中。

Redis 预热

在分布式缓存中,我们通常都是使用 Redis,针对 Redis 的预热,有以下几个工具可供使用,帮助我们实现缓存的预热:

  • RedisBloom:RedisBloom 是 Redis 的一个模块,提供了多个数据结构,包括布隆过滤器、计数器、和 TopK 数据结构等。其中,布隆过滤器可以用于 Redis 缓存预热,通过将预热数据添加到布隆过滤器中,可以快速判断一个键是否存在于缓存中
  • Redis Bulk loading:这是一个官方出的,基于 Redis 协议批量写入数据的工具
  • Redis Desktop Manager:Redis Desktop Manager 是一个图形化的 Redis 客户端,可以用于管理 Redis 数据库和进行缓存预热。通过 Redis Desktop Manager,可以轻松地将预热数据批量导入到 Redis 缓存中。

应用启动时预热

ApplicationReadyEvent

在应用程序启动时,可以通过监听应用启动事件,或者在应用的初始化阶段,将需要缓存的数据加载到缓存中。

ApplicationReadyEvent 是 Spring Boot 框架中的一个事件类,它表示应用程序已经准备好接收请求,即应用程序已启动且上下文已刷新。这个事件是在 ApplicationContext 被初始化和刷新,并且应用程序已经准备好处理请求时触发的。

基于ApplicationReadyEvent,我们可以在应用程序完全启动并处于可用状态后执行一些初始化逻辑。使用 @EventListener 注解或实现 ApplicationListener 接口来监听这个事件。例如,使用 @EventListener 注解:

@EventListener(ApplicationReadyEvent.class)
public void preloadCache() 
{
    // 在应用启动后执行缓存预热逻辑
    // ...
}

Runner

如果你不想直接监听 ApplicationReadyEvent,在 SpringBoot 中,也可以通过 CommandLineRunnerApplicationRunner 来实现这个功能。

CommandLineRunnerApplicationRunner 是 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 {
        // 在应用启动后执行缓存预热逻辑
        // ...
    }
}

CommandLineRunnerApplicationRunner的调用,是在 SpringApplicationrun 方法中

其实就是 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 接口

实现 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 注解

类似的,我们还可以使用 @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开源项目合集
    免费分享无套路,有帮助点个赞就好!

    相关推荐

  • 来挖转转漏洞啦!
  • 老黄祭出新核弹B200!30倍H100单机可训15个GPT-4模型,AI迎新摩尔时代
  • 建议!千万不要再无脑背八股文了!
  • 恭喜了!全体程序员彻底狂欢吧!这个好消息来得太及时!
  • 马斯克兑现承诺,Grok模型携3140亿参数强势开源,商业用途全免费!
  • 陶大程团队联合港大等发布最新综述:374篇文献全面解析大模型知识蒸馏
  • 黄仁勋在AI界春晚GTC2024的主旨演讲: 精华及全文(附视频)
  • CVPR 2024 | 港理工联合OPPO提出统一且通用的视频分割大模型
  • CVPR最佳论文颁给自动驾驶大模型!LLM能突破行业技术“天花板”吗?
  • 过去一年大模型有哪些突破性技术发展?
  • 达观数据知识图谱增强的大模型应用实践
  • 十分钟验证一个高性能车联网数据平台解决方案
  • 作为技师,要求精通一门编程语言很合理吧
  • 云原生消息流系统Apache RocketMQ在腾讯云的大规模生产实践
  • 开源日报 | 微软AI程序员登场,马斯克开源Grok;Open-Sora全面开源
  • 英伟达全新GPU架构Blackwell——“全球最强”、第二代Transformer引擎、计算性能提升1000倍
  • 为逼迫离职,把工位安排到厕所旁,该员工记录“领导如厕时间”发大群
  • 我...,竟然给一个默认人充了一年话费,才发现。。。
  • Git零基础实战之如何实现子项目同步更新
  • 【深度学习】基于深度学习的目标检测算法综述