需求背景
项目的一个导入功能,但是需要远程调用其他域,开始是java循环调用出现了超时的情况,考虑采用异步多线程的方式,分批次多线程调用中台,批量调用中台的逻辑结束之后根据反参才能执行主线程的后续逻辑。这里采用了异步线程的钩子方法,具体参考下面的方法实现。
具体伪代码实现
获取excel的数据——>根据获取的数据集合计算需要开启的任务数——>开启多任务处理器——>编写实现类实现多任务处理——>多任务处理之后执行executes的钩子方法
public String importSalesInformation(String reqJson) {
com.cic.businsurance.channeladapt.domain.vo.ResultModel resultModel = new com.cic.businsurance.channeladapt.domain.vo.ResultModel();
resultModel.setSuccess(false);
resultModel.setStatus(0);
SalesTempRequest salesTempRequest = JSONObject.parseObject(reqJson, SalesTempRequest.class);
ReqFileId reqFileId = new ReqFileId();
reqFileId.setFileId(salesTempRequest.getFileId());
//下载文件
File file = fileDownLoad.fileDownload(reqFileId);
InputStream inputStream = null;
try{
Workbook workBook = null;
//读取文件流
inputStream = this.getFileInputStream(file);
//将EXCEl文件转换为实体对象
List<SaleInfoTempReq> excelToData = this.getExcelToData(inputStream, workBook);
List<SaleInfoImport> saleInfoList = new ArrayList<>();
List<TManagerAgent> agentDtoListSave = new ArrayList<>();
//全部数据处理需要分的批次,也就是需要启用几个线程
Map<Integer, List<SaleInfoTempReq>> partData = new PartThreadData().getPartData(excelToData);
List<Future<Map<String,Object>>> executes = new ArrayList<>();
//多线程分批校验
for (int k =0;k < partData.size();k++) {
List<SaleInfoTempReq> saleInfoTempReq = partData.get(k); executes.add(asyncTaskFuture.taskSalesInfoValidate(saleInfoTempReq,saleInfoList,agentDtoListSave));
}
//根据所有数据执行校验情况,判断是否执行保存逻辑,如果有一个不通过校验,则不执行保存逻辑
Boolean isSave = true;
while (executes!=null && executes.size()>0) {
for(Future<Map<String,Object>> future : executes){
if (future.isDone() && !future.isCancelled()) {//获取future成功完成状态,如果想要限制每个任务的超时时间,取消本行的状态判断+future.get(1000*1, TimeUnit.MILLISECONDS)+catch超时异常使用即可。
//获取执行结果
if("0".equals(future.get().get("flag"))){
isSave=false;
}
executes.remove(future);
break;//当前future获取结果完毕,跳出while
} else {
Thread.sleep(10);//每次轮询休息10毫秒(CPU纳秒级),避免CPU高速轮循耗空CPU---》新手别忘记这个
}
}
}
//全部数据校验通过则调用保存销售信息接口
if (isSave&&excelToData.size()==saleInfoList.size()){
long saveBatchStart = System.currentTimeMillis();
saleInfoRepository.saveBatch(saleInfoList);
tManagerAgentRepository.saveBatch(agentDtoListSave);
long saveBatchEnd = System.currentTimeMillis();
LogUtils.error(msg_log,"销售信息导入表用时==========" + (saveBatchEnd - saveBatchStart));
resultModel.setSuccess(true);
resultModel.setStatus(1);
resultModel.setData("导入成功!已成功导入"+ saleInfoList.size() +"条销售信息!");
}else {
String fileId = excelUtil.getFileId(excelToData);
resultModel.setSuccess(true);
resultModel.setStatus(1);
resultModel.setData(fileId);
throw new RuntimeException("数据校验不通过!");
}
}catch (Exception e) {
LogUtils.error(msg_log,"销售业务信息Excel导入失败{0}"+e.getMessage());
resultModel.setErrorMessage("文件导入失败,"+e.getMessage());
return JSONObject.toJSONString(resultModel);
} finally {
if(null!= inputStream){
try{
inputStream.close();
}catch(Exception e){
LogUtils.error(error_log,"关闭流失败{0}",e.getMessage());
throw new RuntimeException("文件流关闭失败,服务异常");
}
}
}
return JSONObject.toJSONString(resultModel);
}
计算需要线程数
package com.cic.businsurance.channeladapt.domain.util;
import com.cic.businsurance.channeladapt.domain.entity.importsalesinfo.SaleInfoTempReq;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description TODO
* @Author wangchengzhi
* @Date 2023/9/14 10:11
*/
@Service
public class PartThreadData {
//每个线程处理的条数
private int limitCount= 70;
public Map<Integer, List<SaleInfoTempReq>> getPartData(List<SaleInfoTempReq> excelToData){
int part = 0;
//使用线程下标划分,每个线程需要处理的数据
Map<Integer, List<SaleInfoTempReq>> dealPartMap = new HashMap<>();
//需要分几个线程处理
if (excelToData.size() % limitCount != 0) {
part = excelToData.size() / limitCount + 1;
} else {
part = excelToData.size() / limitCount;
}
List<SaleInfoTempReq> limitList = null;
//每个线程需要处理的数据
for (int k = 0; k < part; k++) {
if (excelToData.size() <= (k + 1) * limitCount) {
limitList = excelToData.subList(k * limitCount, excelToData.size());
} else {
limitList = excelToData.subList(k * limitCount, (k + 1) * limitCount);
}
dealPartMap.put(k, limitList);
}
return dealPartMap;
}
}
多任务执行器的实现
/**
* cic.com Inc. Copyright (c) 2004-2021 All Rights Reserved.
*/
package com.cic.businsurance.channeladapt.domain.util;
import com.cic.businsurance.channeladapt.domain.entity.SaleInfoImport;
import com.cic.businsurance.channeladapt.domain.entity.TManagerAgent;
import com.cic.businsurance.channeladapt.domain.entity.importsalesinfo.SaleInfoTempReq;
import com.cic.businsurance.channeladapt.domain.service.importsalesinfo.ImportSalesInformationServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
@Component
@Slf4j
public class AsyncTaskFuture {
@Autowired
private ImportSalesInformationServiceImpl importSalesInformationService;
@Async("taskExecutor")
public Future<Map<String,Object>> taskSalesInfoValidate(List<SaleInfoTempReq> excelData, List<SaleInfoImport> saleInfoList, List<TManagerAgent> agentDtoListSave){
return new AsyncResult<Map<String,Object>>(importSalesInformationService.checkExcelSaleInfo(excelData, saleInfoList, agentDtoListSave));
}
}
钩子方法处理处理反参
while (executes!=null && executes.size()>0) {
for(Future<Map<String,Object>> future : executes){
if (future.isDone() && !future.isCancelled()) {//获取future成功完成状态,如果想要限制每个任务的超时时间,取消本行的状态判断+future.get(1000*1, TimeUnit.MILLISECONDS)+catch超时异常使用即可。
//获取执行结果
if("0".equals(future.get().get("flag"))){
isSave=false;
}
executes.remove(future);
break;//当前future获取结果完毕,跳出while
} else {
Thread.sleep(10);//每次轮询休息10毫秒(CPU纳秒级),避免CPU高速轮循耗空CPU---》新手别忘记这个
}
}
}
用while是为了阻断主线程,等待多任务处理之后的反参判断,处理后续的逻辑
@EnableAsync启动类开启异步多任务处理器
package com.cic.businsurance.channeladapt;
import com.aliyun.fsi.insurance.shared.common.constant.LoggerConstant;
import com.aliyun.fsi.insurance.shared.common.util.log.LogUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class,scanBasePackages = {"com.cic.businsurance.channeladapt","com.aliyun.fsi.insurance.file"})
@EnableCaching
@MapperScan("com.cic.businsurance.channeladapt.repository.mysql.mapper")
//开启异步调用
@EnableAsync
public class SystemaApplication {
public static void main(final String[] args) {
SpringApplication application = new SpringApplication(SystemaApplication.class);
application.run(args);
}
}
** 以上为记录一次项目多任务处理器的需求处理全流程,供大家学习参考,欢迎大家留言交流。**