先回答一下标题, 原因很简单, 踩坑.
很久很久很久之前, 有一段祖传代码, 有多久? 久到离谱... 那时候的世界很简单, 人们都玩着单机游戏....好, 回归正题, 先让看看大家看看祖传代码
System.out.println("开始全量计算企业评价结果.updateAllXyztEvaResult");
// PreparedStatement ps = null;
//ResultSet rs = null;
//Connection conn = null;
try {
//conn = jdbcTemplate.getDataSource().getConnection();
String querySql = "SELECT MASTERDATAID,ZTMC FROM " + clscode;
List<Map<String, Object>> list=jdbcTemplate.queryForList(querySql);
int i = 0;
System.out.println("开始循环计算...");
for (Map<String, Object> map : list) {
//省略大概500行..根据map对象获取数据和根据模型计算/还有递归...
}
}
别急着吐槽, 那个年代单表也就几百上千的数据, 单机循环跑挺安稳的....
随着时代的变化, 数据量的膨胀, 单表从几百上千到几万上几十万(几十万就膨胀了? 大厂都笑了)
因为咱们的业务不需要实时计算, 计算周期可以非常的长...但是为了效率, 咱们还是把多线程加了进来, 为了避免一次查询过多内容导致内存占用太大, 每查到1000个计算主体就生成一个任务, 放到线程池队列里面, 单机跑着定时任务, 核心也是那段祖传代码, 就长出了第二个版本. . 大概是这样的:
效率也还行, 在各个业务项目里面跑着也没发生什么问题..没记错的话还支撑过超200万的主体表..
然后到了最近的某个项目, 也还是这个配方, 某位不愿意透露真实姓名的汤同学却是一筹莫展..只要应用发布到生产环境,运行起来就会挂...日志打印看着运行速度还是很快的, 但就是过一会就内存满了, 崩了. 本地测试也都没问题, 运行正常, 整天喃喃吐槽服务器. CPU差,内存少, 磁盘慢, 人都不好了..刨去了N多与本方法无关的代码之后, 翻到了核心代码:
int page = 0;
boolean hasData = true;
while (hasData) {
int currLimit = page == 0 ? 0 : page * 1000 - 1;
//TODO List<Map<String, Object>> pageList 是否可以使用Lucene (注意:(lucene)更新可能冲突导致查询不到数据;)
List<Map<String, Object>> pageList = jdbcTemplate.queryForList("select columns... from " + sourceTable + " order by id limit " + currLimit + ", " + 1000);
if (pageList.isEmpty()) {
hasData = false;
} else {
page++;
//将多线程放到最小的颗粒上;
for (Map<String, Object> map : pageList) {
executor.execute(new EvaResultCalculateThreadGDVersion(redisTemplate,evaModel, itemTree, map, batchNo));
}
pageList.clear();
}
}
executor是初始化了10个线程... 每读取到1000行记录就生成一个计算任务对象放到线程池里跑...
你能看出来什么问题么? 坦白说我也看了很久...