项目中- 多线程异步 Future 使用

一. 项目中架构要求:

        外围客户端系统 -  交易中心微服务(场景微服务) - 交易集市微服务/能力中心 - 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  测试结果

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值