当前位置: 首页 > news >正文

Spring Boot 启动时将数据库数据预加载到 Redis 缓存

在这里插入图片描述

Spring Boot 启动时将数据库数据预加载到 Redis 缓存

在实际项目开发中,我们经常需要在应用启动时将一些固定的、频繁访问的数据从数据库预加载到 Redis 缓存中,以提高系统性能。本文将介绍几种实现方案。

方案一:使用 @PostConstruct 注解

这是最简单直接的方式,在 Spring Bean 初始化完成后执行预加载逻辑。

@Component
@Slf4j
public class CachePreloader {@Autowiredprivate UserService userService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@PostConstructpublic void preloadCache() {log.info("开始预加载缓存数据...");try {// 加载用户基础信息List<User> users = userService.getAllActiveUsers();for (User user : users) {String key = "user:" + user.getId();redisTemplate.opsForValue().set(key, user, Duration.ofHours(24));}// 加载系统配置List<SystemConfig> configs = systemConfigService.getAllConfigs();for (SystemConfig config : configs) {String key = "config:" + config.getKey();redisTemplate.opsForValue().set(key, config.getValue(), Duration.ofDays(7));}log.info("缓存预加载完成,共加载 {} 条用户数据,{} 条配置数据", users.size(), configs.size());} catch (Exception e) {log.error("缓存预加载失败", e);}}
}

方案二:实现 ApplicationRunner 接口

ApplicationRunner 在应用启动完成后执行,可以获取命令行参数,更适合复杂的初始化逻辑。

@Component
@Order(1) // 设置执行顺序
@Slf4j
public class CacheApplicationRunner implements ApplicationRunner {@Autowiredprivate DataPreloadService dataPreloadService;@Overridepublic void run(ApplicationArguments args) throws Exception {log.info("ApplicationRunner 开始执行缓存预加载...");// 检查是否需要预加载if (args.containsOption("skip-cache-preload")) {log.info("跳过缓存预加载");return;}dataPreloadService.preloadAllCache();log.info("ApplicationRunner 缓存预加载完成");}
}

方案三:监听 ApplicationReadyEvent 事件

这种方式在应用完全启动就绪后执行,是最安全的预加载时机。

@Component
@Slf4j
public class CacheEventListener {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate ProductService productService;@EventListenerpublic void handleApplicationReady(ApplicationReadyEvent event) {log.info("应用启动完成,开始预加载缓存...");// 异步执行预加载,避免阻塞启动CompletableFuture.runAsync(() -> {try {preloadProductCache();preloadCategoryCache();} catch (Exception e) {log.error("异步预加载缓存失败", e);}});}private void preloadProductCache() {List<Product> hotProducts = productService.getHotProducts();hotProducts.forEach(product -> {String key = "hot_product:" + product.getId();redisTemplate.opsForValue().set(key, product, Duration.ofHours(12));});log.info("热门商品缓存预加载完成,共 {} 条", hotProducts.size());}private void preloadCategoryCache() {List<Category> categories = productService.getAllCategories();redisTemplate.opsForValue().set("all_categories", categories, Duration.ofDays(1));log.info("商品分类缓存预加载完成");}
}

方案四:创建专门的预加载服务

将预加载逻辑封装成独立的服务,便于管理和测试。

@Service
@Slf4j
public class DataPreloadService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate DictService dictService;@Autowiredprivate RegionService regionService;/*** 预加载所有缓存数据*/public void preloadAllCache() {long startTime = System.currentTimeMillis();try {// 并行加载多种数据CompletableFuture<Void> dictFuture = CompletableFuture.runAsync(this::preloadDictCache);CompletableFuture<Void> regionFuture = CompletableFuture.runAsync(this::preloadRegionCache);// 等待所有任务完成CompletableFuture.allOf(dictFuture, regionFuture).join();long endTime = System.currentTimeMillis();log.info("所有缓存预加载完成,耗时: {} ms", endTime - startTime);} catch (Exception e) {log.error("缓存预加载过程中发生异常", e);}}/*** 预加载数据字典*/private void preloadDictCache() {try {Map<String, List<DictItem>> dictMap = dictService.getAllDictItems();dictMap.forEach((dictType, items) -> {String key = "dict:" + dictType;redisTemplate.opsForValue().set(key, items, Duration.ofDays(30));});log.info("数据字典缓存预加载完成,共 {} 种类型", dictMap.size());} catch (Exception e) {log.error("数据字典缓存预加载失败", e);}}/*** 预加载地区数据*/private void preloadRegionCache() {try {List<Region> regions = regionService.getAllRegions();redisTemplate.opsForValue().set("all_regions", regions, Duration.ofDays(30));// 按层级缓存Map<Integer, List<Region>> regionsByLevel = regions.stream().collect(Collectors.groupingBy(Region::getLevel));regionsByLevel.forEach((level, levelRegions) -> {String key = "regions_level:" + level;redisTemplate.opsForValue().set(key, levelRegions, Duration.ofDays(30));});log.info("地区数据缓存预加载完成,共 {} 条记录", regions.size());} catch (Exception e) {log.error("地区数据缓存预加载失败", e);}}
}

配置文件控制

application.yml 中添加配置项,控制预加载行为:

app:cache:preload:enabled: trueasync: truetimeout: 30000  # 超时时间(毫秒)batch-size: 1000  # 批处理大小

对应的配置类:

@ConfigurationProperties(prefix = "app.cache.preload")
@Data
public class CachePreloadProperties {private boolean enabled = true;private boolean async = true;private long timeout = 30000;private int batchSize = 1000;
}

最佳实践建议

1. 异常处理

预加载过程中的异常不应该影响应用启动:

@PostConstruct
public void preloadCache() {try {// 预加载逻辑} catch (Exception e) {log.error("缓存预加载失败,但不影响应用启动", e);// 可以发送告警通知}
}

2. 分批处理

对于大量数据,应该分批处理避免内存溢出:

private void preloadLargeDataset() {int pageSize = cachePreloadProperties.getBatchSize();int pageNum = 0;while (true) {List<DataEntity> dataList = dataService.getDataByPage(pageNum, pageSize);if (dataList.isEmpty()) {break;}// 批量写入 RedisdataList.forEach(data -> {String key = "data:" + data.getId();redisTemplate.opsForValue().set(key, data, Duration.ofHours(6));});pageNum++;log.info("已预加载第 {} 批数据,本批 {} 条", pageNum, dataList.size());}
}

3. 监控和告警

添加预加载状态监控:

@Component
public class CachePreloadMonitor {private final MeterRegistry meterRegistry;private final Counter preloadSuccessCounter;private final Counter preloadFailureCounter;public CachePreloadMonitor(MeterRegistry meterRegistry) {this.meterRegistry = meterRegistry;this.preloadSuccessCounter = Counter.builder("cache.preload.success").register(meterRegistry);this.preloadFailureCounter = Counter.builder("cache.preload.failure").register(meterRegistry);}public void recordSuccess(String cacheType, long count) {preloadSuccessCounter.increment();Gauge.builder("cache.preload.count").tag("type", cacheType).register(meterRegistry, count, Number::doubleValue);}public void recordFailure(String cacheType) {preloadFailureCounter.increment();}
}

总结

选择合适的预加载方案需要考虑以下因素:

  • @PostConstruct: 适合简单的预加载逻辑
  • ApplicationRunner: 适合需要命令行参数的场景
  • ApplicationReadyEvent: 最安全的预加载时机
  • 专门的服务: 适合复杂的预加载需求

无论选择哪种方案,都要注意异常处理、性能优化和监控告警,确保预加载过程不会影响应用的正常启动和运行。

http://www.dtcms.com/a/419226.html

相关文章:

  • Nginx 502 Bad Gateway从 upstream 日志到 FastCGI 超时深度复盘
  • NLP自然语言处理性能评估指标
  • 零基础从头教学Linux(Day 43)
  • 网站后期维护协议企业网站建立哪
  • k8s 兼容摩尔线程
  • 网站建设人员工作计划网站定制设计价目表
  • RKD论文阅读
  • 导航类网站模板自己怎么做一个企业官网
  • 广东平台网站建设制作青岛网站设计怎么选
  • 如何破除迷信思维掌握ROS1/ROS2机器人学习的唯物主义
  • 桌面版exe安装和Python命令行安装2种方法详细讲解图片去水印AI源码私有化部署Lama-Cleaner安装使用方法-优雅草卓伊凡
  • C++聊天系统从零到一:CMake构建系统-企业级C++项目的构建利器
  • 折扣影票api?如何选择对接渠道?
  • 移动网站优化宁波企业网站制作公司
  • Oracle 闪回过期后的解决方法
  • 慧博云通受邀参加全球数字贸易博览会,两大出海案例入选“数贸故事”
  • 暴雨山洪灾害的发生与防治虚拟仿真实验
  • 【精品资料鉴赏】400页可编辑word 软件系统通用技术方案及实施方案
  • 学网站开发要多少钱高端品牌手机有哪些
  • 阿里网站怎么建设苏州网站制作排名优化
  • List容器(上)实战探索解析
  • 旅游做的视频网站二手网站排名
  • 灯带富晟 HID发收 源码 C# 三色灯源码和演示 C++
  • 怎么建设自己的论坛网站wordpress修改footer
  • Python 中四种高级特征缩放技术详解:超越标准化的数据预处理
  • TypeScript语法(类型注解:、类型断言as、联合类型|、类型守卫typeof、交叉类型、类型别名type、类型保护is)
  • 做网站时版权怎么写新型网络营销推广方式
  • 机器学习——朴素贝叶斯详解
  • 2025汽车芯片有哪些看点,将会带来哪些黑科技?
  • 管道机器人(in-pipe / in-line)避障