9、缓存预热是提升系统启动后性能的关键手段

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

缓存预热(Cache Preheating) 是提升系统启动后性能的关键手段,尤其适用于:

  • 系统重启后缓存为空
  • 每日定时刷新热点数据
  • 大促前预加载商品信息

下面我将为你提供一份 详细、生产级的缓存预热实现方案,适用于 Spring Boot + Redis + Caffeine 的多级缓存架构。


📚 Spring Boot 缓存预热(Cache Preheating)完整实现指南

适用场景:系统启动时、定时任务、大促前预加载热点数据
技术栈:Spring Boot 3 + Redis + Caffeine + Redisson + Scheduled Task


一、什么是缓存预热?

缓存预热 是指在系统启动或特定时间点,主动将热点数据从数据库加载到缓存中,避免首次访问时缓存未命中,导致数据库压力激增。

✅ 典型场景

场景说明
系统重启后避免“缓冷启动”导致雪崩
每日凌晨预加载昨日热门商品/用户
大促前(如双11)提前加载活动商品信息
新功能上线预热核心配置数据

二、预热策略选择

策略说明推荐
启动时预热@PostConstructCommandLineRunner✅ 推荐
定时预热@Scheduled 定时任务✅ 推荐
事件驱动预热监听消息(Kafka/RabbitMQ)触发可选
手动触发预热提供 API 接口手动调用✅ 建议保留

三、实现方式一:系统启动时预热(推荐)

✅ 使用 CommandLineRunner

// service/CachePreheatService.java
package com.example.service;

import com.example.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 缓存预热服务:系统启动时加载热点数据
 */
@Service
public class CachePreheatService implements CommandLineRunner {

    @Autowired
    private UserService userService;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void run(String... args) throws Exception {
        // 获取分布式锁,防止多个实例重复预热
        RLock lock = redissonClient.getLock("cache:preheat:lock");
        try {
            if (lock.tryLock(10, 60, TimeUnit.SECONDS)) {
                System.out.println("开始执行缓存预热...");
                preheatHotUsers();
                System.out.println("缓存预热完成!");
            } else {
                System.out.println("其他实例正在预热,跳过...");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 预热热点用户(假设 ID < 1000 的用户为热点)
     */
    private void preheatHotUsers() {
        for (long id = 1; id <= 1000; id++) {
            // 触发 @Cacheable,自动写入 Caffeine 和 Redis
            userService.getUser(id);
            if (id % 100 == 0) {
                Thread.sleep(10); // 避免瞬间压力过大
            }
        }
    }
}

✅ 优势:自动执行,无需人工干预
🔒 安全:使用 Redisson 分布式锁,防止集群环境下重复预热


四、实现方式二:定时预热(每日凌晨)

✅ 使用 @Scheduled

// service/DailyCachePreheatService.java
package com.example.service;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

/**
 * 每日定时缓存预热
 */
@Service
public class DailyCachePreheatService {

    @Autowired
    private CachePreheatService cachePreheatService;

    /**
     * 每天凌晨 2:00 执行预热
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void preheatDaily() {
        System.out.println("开始执行每日缓存预热...");
        cachePreheatService.preheatHotUsers();
        System.out.println("每日缓存预热完成!");
    }
}

⚠️ 注意:需在主类启用定时任务

@SpringBootApplication
@EnableScheduling  // 启用定时任务
public class RedisIntegrationApplication {
    // ...
}

五、实现方式三:手动触发预热(运维接口)

✅ 提供 REST API

// controller/CacheController.java
@RestController
@RequestMapping("/cache")
public class CacheController {

    @Autowired
    private CachePreheatService cachePreheatService;

    @PostMapping("/preheat")
    public String manualPreheat() {
        try {
            cachePreheatService.run(); // 调用预热逻辑
            return "缓存预热已触发";
        } catch (Exception e) {
            return "预热失败: " + e.getMessage();
        }
    }
}

可通过 curl 或运维平台调用:

curl -X POST http://localhost:8080/cache/preheat

六、高级预热策略(生产推荐)

✅ 1. 基于访问日志的热点数据识别

-- 示例:从访问日志表中找出昨日访问 Top 100 用户
SELECT user_id, COUNT(*) as hits
FROM user_access_log
WHERE DATE(access_time) = CURDATE() - INTERVAL 1 DAY
GROUP BY user_id
ORDER BY hits DESC
LIMIT 100;

在预热服务中调用此 SQL 加载热点数据。


✅ 2. 分批加载 + 限流

避免一次性加载太多数据压垮 DB 或 Redis:

private void preheatHotUsers() {
    int batchSize = 50;
    for (int i = 1; i <= 1000; i += batchSize) {
        List<Long> batch = IntStream.rangeClosed(i, Math.min(i + batchSize - 1, 1000))
                .boxed().toList();

        batch.forEach(userService::getUser);

        try {
            Thread.sleep(100); // 每批间隔 100ms
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

✅ 3. 预热进度监控

@Autowired
private MeterRegistry meterRegistry;

private void preheatHotUsers() {
    Counter preheatCounter = meterRegistry.counter("cache.preheat.items");

    for (long id = 1; id <= 1000; id++) {
        userService.getUser(id);
        preheatCounter.increment();
    }
}

可在 Prometheus 中查看预热进度。


七、预热数据来源建议

数据类型预热策略
用户信息预热 VIP 用户、昨日活跃用户
商品信息预热首页推荐、热销商品
配置项预热所有系统配置(@PostConstruct
权限菜单登录时预热,或定时刷新
排行榜每日凌晨预热昨日 Top 100

八、注意事项与最佳实践

项目建议
🔐 分布式锁使用 Redisson 锁防止重复预热
🐢 分批加载避免瞬间压力过大
📊 监控记录预热耗时、数量
🧯 降级预热失败不影响系统启动
🕐 时间选择选择低峰期(如凌晨 2 点)
📦 数据量控制预热数据不宜超过总数据 20%

九、完整预热流程图

系统启动/定时触发
        ↓
获取分布式锁(Redisson)
        ↓
检查是否已预热(可选 Redis 标记)
        ↓
从 DB/日志 获取热点数据 ID 列表
        ↓
分批调用业务方法触发缓存(@Cacheable)
        ↓
更新预热标记 + 监控指标
        ↓
释放锁

十、总结

方式适用场景推荐指数
启动预热系统重启后⭐⭐⭐⭐⭐
定时预热每日刷新热点⭐⭐⭐⭐☆
手动预热运维操作⭐⭐⭐⭐☆
事件预热消息驱动⭐⭐⭐☆☆

👉 推荐组合
启动预热 + 每日定时预热 + 手动接口
分布式锁 + 分批加载 + 监控

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值