一. 项目中架构要求:
外围客户端系统 - 交易中心微服务(场景微服务) - 交易集市微服务/能力中心 - ESB - 后方系统(理财/基金等)。
交易中心需要同时调用交易集市十几个组件/接口。由于通讯时间太长和接口请求太多,考虑使用多线程。
考虑使用非阻塞的多线程类 Future。Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。
二. 项目使用是银行内部代码,不便于展示,当时的案例demo如下 ,亲测。
0 配置类。
package com.dcits.branch.cloud.centerbatch.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* ClassName:KtlDataFileDealCmp
* Description:多线程配置类
* author: xupfb
* date : 2020/12/03 22:39
*/
@Configuration
@EnableAsync
@Slf4j
public class ThreadPoolConfig {
/**
* 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
* 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
* 当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝
*/
private int corePoolSize = 10;
private int maxPoolSize = 20;
/**
* 允许线程空闲时间(单位:默认为秒)
*/
private int keepAliveTime = 60;
/**
* 缓冲队列大小
*/
private int queueCapacity = 500;
private static final String THREAD_NAME_PREFIX = "BatchAsyn-";
public static final String ThreadPoolName = "batchThreadPool";
/*性能测试发现存在瓶颈。好想是因为gravity也用,发现现象是大并发时线程全部变成主线程在处理了
* 从系统稳定性考虑比较合适。
* */
@Bean(ThreadPoolName)
public ThreadPoolTaskExecutor batchThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveTime);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
executor.initialize();
log.info("create batchThreadPool: {}", executor);
return executor;
}
}
1.1 . 主要吊起类,注入需要得所有得单个方法。
import com.dcits.gravity.api.annotation.GlobalType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* ClassName:个人网银数据入库处理类
* Description:文件下载,从数仓 下载文件到本地
* author: xupfb
* date : 2020/11/13 22:39
*/
@Slf4j
@Component
public class KtlDealEUserDataCmp {
@Autowired
private KtlDealEUsePUserBlueKeyCertCmp ktlDealEUsePUserBlueKeyCertCmp;
@Autowired
private KtlDealEUsePUserCertCmp ktlDealEUsePUserCertCmp;
@Autowired
private KtlDealEUserPAccountCmp ktlDealEUserPAccountCmp;
@Autowired
private KtlDealEUserPBankCifCmp ktlDealEUserPBankCifCmp;
@Autowired
private KtlDealEUserPCifCmp ktlDealEUserPCifCmp;
@Autowired
private KtlDealEUserPUserCmp ktlDealEUserPUserCmp;
@Autowired
private KtlDealEUseUnifiedUserCmp ktlDealEUseUnifiedUserCmp;
/**
* EUSER 个人网银数据处理入库
*
* 从数据仓库提供的文件路径下面,复制文件到本地操作服务器路径。
* 然后使用Kettle 把文件数据截取到对应列,导入到数据库。
* 本期是导入手机银行和个人网银系统的备份表数据
*/
@******Component(
navigationMenu = "/22数据迁移批量业务组件/下载文件", scope = GlobalType.NONE, fillColor = "",
name = "个网文件处理入库",
desc = "Ktl处理文件个人网银数据入库"
)
public void ktlFileDealEUser() {
log.info("------------Begin 开始执行-KTL文件-个网数据处理-----------------------");
// 1 个人蓝牙key证书信息
Future<Boolean> pUserBlueKeyCert = ktlDealEUsePUserBlueKeyCertCmp.ktlFileDealEUserPUserBlueKeyCert();
// 2 个人网银证书信息
Future<Boolean> pUserCert = ktlDealEUsePUserCertCmp.ktlFileDealEUserPUserCert();
// 3 个人机构信息表
Future<Boolean> pBankCif = ktlDealEUserPBankCifCmp.ktlFileDealEUserPBankCif();
// 4 个人客户信息表
Future<Boolean> pCif = ktlDealEUserPCifCmp.ktlFileDealEUserPCif();
// 5 个人网银登录信息表
Future<Boolean> pUser = ktlDealEUserPUserCmp.ktlFileDealEUserPUser();
// 6 个人账户表
Future<Boolean> pAccount = ktlDealEUserPAccountCmp.ktlFileDealEUserPAccount();
// 7 统一登录客户信息
Future<Boolean> unifiedUser = ktlDealEUseUnifiedUserCmp.ktlFileDealEUserPUserCert();
/* V get(long timeout, TimeUnit unit) 设置取结果超时时间 */
try {
Boolean pUserBlueKeyCertResult = pUserBlueKeyCert.get();
Boolean pUserCertResult = pUserCert.get();
Boolean pBankCifResult = pBankCif.get();
Boolean pCifResult = pCif.get();
Boolean pUserResult = pUser.get();
Boolean pAccountResult = pAccount.get();
Boolean unifiedUsertResult = unifiedUser.get();
log.info("个网数据执行完成》》》》》》》》》》》》 1-pUserBlueKeyCertResult == " + pUserBlueKeyCertResult +
" ,2-pUserCertResult == " + pUserCertResult +
" ,3-pBankCifResult == " + pBankCifResult +
" ,4-pCifResult == " + pCifResult +
" ,5-pUserResult == " + pUserResult +
" ,6-pAccountResult == " + pAccountResult +
" ,7-unifiedUsertResult == " + unifiedUsertResult
);
} catch (InterruptedException e) {
log.debug("InterruptedException: {}", e.getMessage());
} catch (ExecutionException e) {
log.debug("ExecutionException: {}", e.getMessage());
}
}
}
1.2 单个处理业务得单独方法。
package com.dcits.branch.cloud.centerbatch.component.batchcomm;
import com.dcits.branch.cloud.centerbatch.bc.kettle.CommonExecuteKtl;
import com.dcits.branch.cloud.centerbatch.dao.repository.EuserPaccountDao;
import com.dcits.branch.cloud.components.exception.BusiException;
import com.dcits.branch.cloud.core.tool.manager.ToolBox;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.concurrent.Future;
/**
* ClassName:个人账户表
* Description:文件下载,从数仓 下载文件到本地
* author: xupfb
* date : 2020/11/13 22:39
*/
@Slf4j
@Component
public class KtlDealEUserPAccountCmp {
@Value("${dcits.batch.sftp.target.url}")
private String targetUrl;
@Autowired
private CommonExecuteKtl commonExecuteKtl;
@Autowired
private EuserPaccountDao euserPaccountDao;
/**
* 从数据仓库提供的文件路径下面,复制文件到本地操作服务器路径。
* 然后使用Kettle 把文件数据截取到对应列,导入到数据库。
* 本期是导入手机银行和个人网银系统的备份表数据
*/
@Async("batchThreadPool")
public Future<Boolean> ktlFileDealEUserPAccount() {
if (StringUtils.isEmpty(targetUrl)) {
throw new BusiException("812002");
}
// 获取当前日期 yyyyMMdd
String yearMonthDay = ToolBox.DATE.datenow();
// 组装目标文件路径, E:\target\ + 20201201 + 需要处理的dat文件
String target = targetUrl + File.separator + yearMonthDay + File.separator;
log.debug("--------------target= {}", target);
// 个人账户表
String ktlName = "EUSER_PACCOUNT_F.ktr";
String fileName = "S_PEB_EUSER_PACCOUNT_F_" + yearMonthDay + ".dat";
Boolean ktlRes = false;
String targetFileName = target + fileName;
log.debug("--------------targetFileName= {}", targetFileName);
// 清空表数据 euserPaccountDao
euserPaccountDao.deleteAllDate();
ktlRes = commonExecuteKtl.executeKtrFile(ktlName, fileName, target);
log.info("调用 kettle 执行结果,{}",ktlRes);
return new AsyncResult<>(ktlRes);
}
}
--------------------------------------------------------------------------------------------------------------------
-- 下面是 自定义 Demo测试。。。。。。
2.2 模拟出入参数类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TaskInfo {
private String taskId;
private String taskName;
}
2.3 模拟实现逻辑
2.3.1 控制层类
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncController {
private Logger logger = LoggerFactory.getLogger(AsyncController.class);
@Autowired
private TaskLogService taskLogService;
@GetMapping(value = "/task" )
public String taskExecute(){
TaskInfo taskInfo1 = new TaskInfo("task1", "taskName001");
TaskInfo taskInfo2 = new TaskInfo("task2", "taskName001");
long startTime = System.currentTimeMillis();
Future<TaskInfo> future1 = null;
Future<TaskInfo> future2 = null;
try {
future1 = taskLogService.insertTaskLog(taskInfo1);
future2 = taskLogService.updateTaskLog(taskInfo2);
// 异步线程池执行 等待执行完成 isDone() 进行判断
/*while (true) {
if (future1.isDone() && future2.isDone()) {
System.out.println("异步任务一、二已完成");
break;
}
}*/
} catch (Exception e) {
log.debug("执行异步任务异常 {}" + e.getMessage());
}
/* V get(long timeout, TimeUnit unit) 设置取结果超时时间 */
TaskInfo result1 = null;
TaskInfo result2 = null;
try {
result1 = future1.get(3, TimeUnit.SECONDS);
result2 = future2.get();
log.debug("任务一result1 == " + result1 + " 任务二result2 == " + result2);
} catch (InterruptedException e) {
log.debug("InterruptedException: {}", e.getMessage());
} catch (ExecutionException e) {
log.debug("ExecutionException: {}", e.getMessage());
} catch (TimeoutException e) {
log.debug("接口get取值超时: {}", e.getMessage());
}
long endTime = System.currentTimeMillis();
log.debug("异步任务总耗时: " + (endTime - startTime));
return result1 + " --- " + result2;
}
}
2.3.2 接口类
import java.util.concurrent.Future;
public interface TaskLogService {
Future<TaskInfo> insertTaskLog(TaskInfo taskInfo) throws InterruptedException;
Future<TaskInfo> updateTaskLog(TaskInfo taskInfo) throws InterruptedException;
}
2.3.3 实现类
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
@Service
public class TaskLogServiceImpl implements TaskLogService {
/**
* 需要进行多线程任务的方法
* 1. 方法注解 @Async("taskExecutor") ,括号指定 线程池 名称
* 2. 方法返回值类型为 AsyncResult<T>
* 注意: 方法返回值类型由 Future 进行包装,即 Future<T>, 返回对象为 AsyncResult<T>
* @param taskInfo
* @return Future<String>
* @throws InterruptedException
*/
@Override
@Async("taskExecutor")
public Future<TaskInfo> insertTaskLog(TaskInfo taskInfo) throws InterruptedException {
System.out.println("1---------currentThread: " + Thread.currentThread() );
System.out.println("任务一 Thread Sleep 2s Start, " + taskInfo.getTaskId());
Thread.sleep(2000);
taskInfo.setTaskId("001-testId");
taskInfo.setTaskName("001-teatName");
System.out.println("任务一 Thread Sleep 2s End, " + taskInfo.getTaskId());
return new AsyncResult<>( taskInfo );
}
@Override
@Async("taskExecutor")
public Future<TaskInfo> updateTaskLog(TaskInfo taskInfo) throws InterruptedException {
System.out.println("2---------currentThread: " + Thread.currentThread() );
System.out.println("任务二 Thread Sleep 5s Start, " + taskInfo.getTaskId());
Thread.sleep(5000);
taskInfo.setTaskId("002-testId");
taskInfo.setTaskName("002-teatName");
System.out.println("任务二 Thread Sleep 5s End, " + taskInfo.getTaskId());
return new AsyncResult<>( taskInfo );
}
}
2.4 测试结果