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