困扰了大半个月的坑爹问题总算解决了,也算是初步学会用Jvm VisualVM排查问题了,写个笔记记录一下。
用Quartz高频率定时执行MongoDB和Mysql数据库的增删改查操作,一个任务对应一个业务。
由于对MongoTemplate的不熟悉,导致在多线程增删改查的时候每执行一轮任务都会重新new一个MongoTemplate,而这个MongoTemplate并不会自己释放掉,造成旧的一直不释放,新的一直在new的情况,导致服务运行到一定的时间必然会因为线程或者连接池爆掉而挂掉,最坑爹的不是它一个服务挂掉,是跟它部署在一个服务器上的所有服务都会挂。
以下是已经改正后的其中一个任务的业务代码,代码比较粗糙,大佬们多多指教
package com.tianfu.system.agent.jobs;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.tianfu.system.agent.dto.*;
import com.tianfu.system.agent.mapper.MYieldEfficiencyMapper;
import com.tianfu.system.agent.utils.VeDate;
import lombok.SneakyThrows;
import org.quartz.*;
import org.springblade.core.tool.utils.Func;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.List;
/**
* @Auther hz_qcy
* @Date 2020/3/18 15:39
*/
@Component
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class MinuteShuttleEfficiencyCalc implements Job {
@Autowired
private MYieldEfficiencyMapper mYieldEfficiencyMapper;
@Override
@SneakyThrows
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String tenantCode = jobExecutionContext.getMergedJobDataMap().getString("tenantCode");
String targetApp = jobExecutionContext.getMergedJobDataMap().getString("targetApp");
MongoTemplate mongoTemplate = (MongoTemplate) jobExecutionContext.getMergedJobDataMap().get("mongoTemplate");
//起始米数
String startMeter = "";
//计算时间
String currDate = VeDate.getStringDate();
//班组产量(最大值)
double length = 0;
//运行时间
double runSeconds = 0;
//停机时间
double stopSeconds = 0;
//停机次数
Integer stopCounts = 0;
//运行效率
double efficiency = 0;
//String效率 插入数据库字段
String sEfficiency = "";
//纬密
double weftDensity = 0;
//打纬数
double beating = 0;
//流程卡号
String jobSheetNo = "";
//挡车工
String empCode = "";
//获取班组相关信息
List<ShiftClassDto> shiftClassDtoList = mYieldEfficiencyMapper.getShiftClassDto(tenantCode, VeDate.getUserDate("yyyy-MM-dd"));
for (ShiftClassDto shiftClassDto : shiftClassDtoList) {
//根据租户和部门取出机台号集合,以机台为主键进行统计
List<String> machineCodeList = mYieldEfficiencyMapper.getMachineCodeList(tenantCode, shiftClassDto.getDeptCode());
for (String machineCode: machineCodeList) {
List<MStopRecordDto> mStopRecordDtoList = mYieldEfficiencyMapper.getMStopRecordDto(targetApp+".m_stop_record",tenantCode, VeDate.getUserDate("yyyy-MM-dd"), shiftClassDto.getShiftClass(), machineCode);
//查询结果不为空才执行
if (Func.isNotEmpty(mStopRecordDtoList)) {
//纬密
weftDensity = mYieldEfficiencyMapper.getWeftDensity(tenantCode, machineCode);
for (MStopRecordDto mStopRecordDto : mStopRecordDtoList) {
//产量
length = mStopRecordDto.getLocation();
//反计算打纬数
beating = length * 100 * weftDensity;
//工卡号
jobSheetNo = mStopRecordDto.getJobSheetNo();
//停机状态为0是运行,不为0是停机,要累计停机次数
if (!"0".equals(mStopRecordDto.getStopCause() )) {
String startTime = mStopRecordDto.getStartTime();
String endTime = "";
if (Func.isNotEmpty(mStopRecordDto.getEndTime())) {
endTime = mStopRecordDto.getEndTime();
} else {
endTime = VeDate.getStringDate();
}
//累计停机时间和停机次数
stopSeconds = stopSeconds + VeDate.calculatetimeGapSecond(startTime, endTime);
stopCounts = stopCounts + 1;
} else if ("0".equals( mStopRecordDto.getStopCause())) {
String startTime = mStopRecordDto.getStartTime();
String endTime = "";
if (Func.isNotEmpty(mStopRecordDto.getEndTime())) {
endTime = mStopRecordDto.getEndTime();
} else {
endTime = VeDate.getStringDate();
}
//累计运行时间
runSeconds = runSeconds + VeDate.calculatetimeGapSecond(startTime, endTime);
}
}
//如果运行时间和停机时间都为0则效率为0
if (String.valueOf(0.0).equals(String.valueOf(runSeconds)) && String.valueOf(0.0).equals(String.valueOf(stopSeconds))) {
efficiency = 0;
sEfficiency = String.valueOf(efficiency);
} else {
efficiency = runSeconds / (runSeconds + stopSeconds);
//如果效率大于0保留2位小数
if (efficiency > 0) {
sEfficiency = String.format("%.2f",efficiency);
} else {
sEfficiency = "0";
}
}
//查出起始米数
Query query = new Query();
query.addCriteria(new Criteria().andOperator(Criteria.where("tenant_code").is(tenantCode), Criteria.where("machine_code").is(machineCode), Criteria.where("time").gte(shiftClassDto.getBeginTime()), Criteria.where("time").lte(shiftClassDto.getEndTime()), Criteria.where("type").is("5")));
List<TFShuttleDto> tfShuttleDtoList = mongoTemplate.find(query, TFShuttleDto.class, "TF_" + tenantCode);
if (tfShuttleDtoList.size()>0) {
for (Param param : tfShuttleDtoList.get(0).getGatherData()) {
if ("IN1_Count".equals(param.getParamCode())) {
if (!"0".equals(param.getParamValue())) {
startMeter = String.format("%.2f", param.getParamValue());
} else {
startMeter = "0";
}
}
}
}
//挡车工
empCode = mStopRecordDtoList.get(0).getEmpCode();
//班组时间
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String shiftTime1 = sdf.format(sdf2.parse(shiftClassDto.getBeginTime()));
String shiftTime2 = sdf.format(sdf2.parse(shiftClassDto.getEndTime()));
String shiftTime = shiftTime1 + "-" + shiftTime2;
MYieldEfficiencyDto lastMYieldEfficiencyDto = mYieldEfficiencyMapper.getLastMYieldEfficiencyDto(targetApp+".m_yield_efficiency",tenantCode, machineCode);
String lastJobSheetNo ="";
String lastShiftClass ="";
String lastCurrDate = "";
//如果这个租户的机台还没插入过第一条数据就不存在所谓的最后一条数据
if (Func.isNotEmpty(lastMYieldEfficiencyDto)) {
lastJobSheetNo = lastMYieldEfficiencyDto.getJobSheetNo();
lastShiftClass = lastMYieldEfficiencyDto.getShiftClass();
lastCurrDate = lastMYieldEfficiencyDto.getCurrDate();
}
//新表中会存在最后一条订单号为空的情况,为空直接插入新数据
if (Func.isNotEmpty(lastJobSheetNo)) {
if (lastJobSheetNo.equals(jobSheetNo) && shiftClassDto.getShiftClass().equals(lastShiftClass) && lastCurrDate.equals(VeDate.getUserDate("yyyy-MM-dd"))) {
mYieldEfficiencyMapper.updateYieldEfficiency(targetApp+".m_yield_efficiency",String.valueOf(length), sEfficiency, runSeconds, stopSeconds, stopCounts, VeDate.getStringDate(), tenantCode, jobSheetNo, shiftClassDto.getShiftClass(), VeDate.getUserDate("yyyy-MM-dd"),machineCode);
} else {
// 如果订单号或者班组不同则插入新的记录
mYieldEfficiencyMapper.insertMYieldEfficiency(targetApp+".m_yield_efficiency",IdWorker.getIdStr(), tenantCode, currDate, machineCode, jobSheetNo, shiftClassDto.getShiftClass(), empCode, shiftClassDto.getBeginTime(), shiftClassDto.getEndTime(), startMeter, String.valueOf(length), sEfficiency, String.valueOf(runSeconds), String.valueOf(stopSeconds), String.valueOf(stopCounts), shiftTime, String.valueOf(beating), VeDate.getStringDate());
}
} else {
// 如果订单号或者班组不同则插入新的记录
mYieldEfficiencyMapper.insertMYieldEfficiency(targetApp+".m_yield_efficiency",IdWorker.getIdStr(), tenantCode, currDate, machineCode, jobSheetNo, shiftClassDto.getShiftClass(), empCode, shiftClassDto.getBeginTime(), shiftClassDto.getEndTime(), startMeter, String.valueOf(length), sEfficiency, String.valueOf(runSeconds), String.valueOf(stopSeconds), String.valueOf(stopCounts), shiftTime, String.valueOf(beating), VeDate.getStringDate());
}
}
}
}
}
}
原来我的MongoTemplate 是通过以下方法new出来的,每次到指定时间都会new一个新的实例出来,如果按这种方法的话,极端情况下譬如1秒执行一次定时任务,那结果就是1秒创建一个MongoTemplate,并且它永远不会释放掉,这个服务就会像一个定时炸弹一样每天定时爆破整个服务器
一直创建MongoTemplate实例,线程数一直蹭蹭往上涨new出来的MongoTemplate用完一次后就一直保持等待状态,除非重启服务不然永远不会被释放掉。这里就可以明确看出MongoDB的连接存在问题
/**
* dye
* @return
* @throws Exception
*/
public synchronized MongoTemplate getMongoTemplateDye() throws Exception {
List<ServerAddress> addressList=new ArrayList<>();
List<MongoCredential> credentials=new LinkedList<>();
MongoCredential mongoCredential=MongoCredential
.createScramSha1Credential(username,source,password.toCharArray());
credentials.add(mongoCredential);
ServerAddress serverAddress=new ServerAddress(ip,port);
addressList.add(serverAddress);
MongoClient mongoClient=new MongoClient(addressList, credentials);
return new MongoTemplate(new SimpleMongoDbFactory(mongoClient,fileDatabaseDye));
}
这两行代码则是解决问题的关键所在,在创建任务的时候就把MongoTemplate实例创建好,然后直接存到Map中,在job任务类中就只要把它取出来直接用就行,这样就能避免重复创建问题了
MongoTemplate mongoTemplate = (MongoTemplate) jobExecutionContext.getMergedJobDataMap().get("mongoTemplate");