SpringBoot启动时将数据库数据预加载到Redis缓存的几种实现方案
目录
- 引言
- 方案一:使用 @PostConstruct 注解
- 方案二:实现 ApplicationRunner 接口
- 方案三:监听 ApplicationReadyEvent 事件
- 方案四:创建专门的预加载服务
- 配置文件控制
- 最佳实践建议
- 1. 异常处理
- 2. 分批处理
- 3. 监控和告警
- 总结
引言
在实际项目开发中,我们经常需要在应用启动时将一些固定的、频繁访问的数据从数据库预加载到 Redis 缓存中,以提高系统性能。本文将介绍几种实现方案。
方案一:使用 @PostConstruct 注解
这是最简单直接的方式,在 Spring Bean 初始化完成后执行预加载逻辑。
@Component @Slf4j public class CachePreloader { @Autowired private UserService userService; @Autowired private RedisTemplate<String, Object> redisTemplate; @PostConstruct public 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); } } }
方案二:实现 ApplicatioAIYrinRunner 接口
ApplicationRunner
在应用启动完成后执行,可以获取命令行参数,更适合复杂的初始化逻辑。
@Component @Order(1) // 设置执行顺序 @Slf4j public class CacheApplicationRunner implements ApplicationRunner { @Autowired private DataPreloadService dataPreloadService; @Override public 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 { @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private ProductService productService;js @EventListener public 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 { @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private DictService dictService; @Autowiredhttp://www.devze.com private 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) -> { S编程客栈tring key = "regions_level:" + level; redisTemplate.opsForValue().set(key, levelRegions, Duration.ofDays(30)); }); log.info("地区数据缓存预加载完成,共 {} 条记录", regions.size());javascript } catch (Exception e) { log.error("地区数据缓存预加载失败", e); } } }
配置文件控制
在 application.yml
中添加配置项,控制预加载行为:
app: cache: preload: enabled: true async: true timeout: 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; } // 批量写入 Redis dataList.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: 最安全的预加载时机
- 专门的服务: 适合复杂的预加载需求
无论选择哪种方案,都要注意异常处理、性能优化和监控告警,确保预加载过程不会影响应用的正常启动和运行。
以上就是SpringBoot启动时将数据库数据预加载到Redis缓存的几种实现方案的详细内容,更多关于SpringBoot数据加载到Redis缓存的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论