背景:从多数据平台rpc拉取数据,整合->计算->入库
问题:内存占用高,计算过程用时过长
数据量不算很大,总共6个远程接口,每个接口数据量在4万条左右,数据库mysql(innodb引擎)
优化步骤:
1.针对关键查询建联合索引(必备步骤)
2.所有需要多次循环对比赋值的地方,list转换成map实现(效果相当明显,本机从2分多钟降至37秒,见代码)
3.远程日志只打印关键信息,如获取结果和条数,无需打印明细(对于任务的速度提升不明显,但是可以节省内存和存储空间)
4.大对象在初始化时指定大小,防止扩容带来的性能开销,在finally里面手动清除,不等待GC回收(此步在数据量多的情况下实用)
5.批量插入数据方式从一次语句插入改为分批次(3000条)插入一次,任务的执行速度发生了质的提升从3分半提升到1分半,内存也运行稳定,保持在1.3G左右,没再发生容器重启的情况
针对第2步的代码实现
//一般写法,嵌套循环比较
List<PubRateTerminalRespDto> sellTerminalList = getSellTerminalListAfterCalc(projectSellTerminalRespList, FinancialConstants.DIMENSION_PROJECT);
List<PubRateTerminalRespDto> pubRateList = getPubRateListAfterCalc(projectPubRateRespList, FinancialConstants.DIMENSION_PROJECT);
for (PubRateTerminalRespDto sellTerminal : sellTerminalList) {
for (PubRateTerminalRespDto pubRate : pubRateList) {
if (sellTerminal.getProjectId().equals(pubRate.getProjectId())) {
sellTerminal.setXjRate(pubRate.getXjRate());
sellTerminal.setCityId(pubRate.getCityId());
sellTerminal.setCrmId(pubRate.getCrmId());
}
}
}
//优化写法,将list转换成map,在流中分组,filter中的内容必不可少,否则会报空指针,比嵌套循环高效许多
List<PubRateTerminalRespDto> pubRateList = getPubRateListAfterCalc(projectPubRateRespList, FinancialConstants.DIMENSION_PROJECT);
//转换为map
Map<String, List<PubRateTerminalRespDto>> pubRateMap = pubRateList.stream().filter(vo -> StringUtils.isNotBlank(vo.getProjectId())).collect(Collectors.groupingBy(PubRateTerminalRespDto::getProjectId));
for (PubRateTerminalRespDto sellTerminal : sellTerminalList) {
if(CollectionUtils.isNotEmpty(pubRateMap.get(sellTerminal.getProjectId()))) {
PubRateTerminalRespDto pubRate = pubRateMap.get(sellTerminal.getProjectId()).get(0);
sellTerminal.setXjRate(pubRate.getXjRate());
sellTerminal.setCityId(pubRate.getCityId());
sellTerminal.setCrmId(pubRate.getCrmId());
}
}
针对第4步:
FinancialConstants.POINTS_NUMS = 40000;
List<DataPlatformResponseDto> projectSellTerminalRespList = new ArrayList<>(FinancialConstants.POINTS_NUMS);
List<PubRateTerminalRespDto> sellTerminalList = new ArrayList<>(FinancialConstants.POINTS_NUMS);
List<DataPlatformPubRateResponseDto> projectPubRateRespList = new ArrayList<>(FinancialConstants.POINTS_NUMS);
List<PubRateTerminalRespDto> pubRateList = new ArrayList<>(FinancialConstants.POINTS_NUMS);
Map<String, List<PubRateTerminalRespDto>> pubRateMap = new HashMap<>(FinancialConstants.POINTS_NUMS);
List<PointsMappingResponseDto> pointsMappingRespList = new ArrayList<>(FinancialConstants.POINTS_NUMS);
Map<String, List<PointsMappingResponseDto>> pointsMappingMap = new HashMap<>(FinancialConstants.POINTS_NUMS);
try{
//业务代码
}finnaly{
projectSellTerminalRespList.clear();
sellTerminalList.clear();
projectPubRateRespList.clear();
pubRateList.clear();
pubRateMap.clear();
pointsMappingRespList.clear();
pointsMappingMap.clear();
}
现在还有点问题:
代码中用了批量插入语句,预发布环境上任务跑一会儿容器会重启,内存问题
insert into points_daily
values
<foreach collection="insertPointsDailyList" item="item" separator=",">
这种写法会拼装sql,4万条执行完成需要1分钟多一点
查看数据库对单语句长度的限制
select @@max_allowed_packet; 配的一个G,也就是说语句拼接到一个G就会报错
进行第5步优化:
private static final int BATCH_COUNT = 3000;
public void batchInsertDaily(List<FinancialPointsDaily> pointsDaily) {
int index = 0;
while (true) {
if (index + BATCH_COUNT >= pointsDaily.size()) {
financialMapper.insertPointsDaily(pointsDaily.subList(index, pointsDaily.size()));
break;
} else {
financialMapper.insertPointsDaily(pointsDaily.subList(index, index + BATCH_COUNT));
index = index + BATCH_COUNT;
}
}
}