引言
在实际项目开发中,我们经常需要在应用启动时将一些固定的、频繁访问的数据从数据库预加载到 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); } } }
方案二:实现 applicationrunner 接口
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; @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; @autowired 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) -> { 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: 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缓存的资料请关注代码网其它相关文章!
发表评论