因为最近项目上线,需要同步期初数据-工序,大概有120万数据,采用之前Mybatis批量插入,一次5000的方式,单线程,大概需要近半个小时,后面为了提高效率,采用多线程编程,速度提升了大概2倍,耗时15分钟,同步120万条数据数
采用的是SpringBoot的多线程和@Async和Future
先了解下概念:
此处引用其他网站的解释:
什么是SpringBoot多线程
Spring是通过任务执行器(TaskExecutor)来实现多线程和并发编程,使用ThreadPoolTaskExecutor来创建一个基于线城池的TaskExecutor。在使用线程池的大多数情况下都是异步非阻塞的。我们配置注解@EnableAsync
可以开启异步任务。然后在实际执行的方法上配置注解@Async
上声明是异步任务
以下写法是单线程一次新增5000数据
public void syncWholeTools() {
// 获取最大的AutoID
Integer maxAutoId = readToolsService.getMaxAutoId();
final Integer batchNumber = 5000;
Integer count = maxAutoId / batchNumber;
Integer currentAotoId = 0;
//分批次新增
for (int i = 0; i < count; i++) {
List readToolModelList = readToolsService.getToolListByAutoId(currentAotoId,
(i + 1) * batchNumber);
currentAotoId = (i + 1) * batchNumber + 1;
writeToolService.createBomDetail(readToolModelList);
}
List readToolModelList = readToolsService.getToolListByAutoId(currentAotoId, maxAutoId);
writeToolService.createBomDetail(readToolModelList);
}
使用Spring Boot多线程
启动类需要加上@EnableAsync注解
yml配置
tools:
core:
poolsize: 100
max:
poolsize: 200
queue:
capacity: 200
keepAlive:
seconds: 30
thread:
name:
prefix: tool
配置类:
@Configuration
@EnableAsync
public class AsyncConfig {
//接收报文核心线程数
@Value("${tools.core.poolsize}")
private int toolsCorePoolSize;
//接收报文最大线程数
@Value("${tools.max.poolsize}")
private int toolsMaxPoolSize;
//接收报文队列容量
@Value("${tools.queue.capacity}")
private int toolsQueueCapacity;
//接收报文线程活跃时间(秒)
@Value("${tools.keepAlive.seconds}")
private int toolsKeepAliveSeconds;
//接收报文默认线程名称
@Value("${tools.thread.name.prefix}")
private String toolsThreadNamePrefix;
/**
* toolsTaskExecutor:(接口的线程池).
*
* @return TaskExecutor taskExecutor接口
* @since JDK 1.8
*/
@Bean(name = "ToolsTask")
public ThreadPoolTaskExecutor toolsTaskExecutor() {
//newFixedThreadPool
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(toolsCorePoolSize);
// 设置最大线程数
executor.setMaxPoolSize(toolsMaxPoolSize);
// 设置队列容量
executor.setQueueCapacity(toolsQueueCapacity);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(toolsKeepAliveSeconds);
// 设置默认线程名称
executor.setThreadNamePrefix(toolsThreadNamePrefix);
// 设置拒绝策略
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
}
数据新增的操作:
@Component
public class SyncToolsHandler {
private static final Logger LOG = LoggerFactory.getLogger(SyncToolsHandler.class);
@Autowired
WriteToolService writeToolService;
@Async(value = "ToolTask")
public Future syncTools(List readToolList, int pageIndex) {
System.out.println("thread name " + Thread.currentThread().getName());
LOG.info(String.format("此批数据的段数为:%s 此段数据的数据条数为:%s", pageIndex, readToolList.size()));
//声明future对象
Future result = new AsyncResult ("");
//循环遍历
if (null != readToolList && readToolList.size() > 0) {
try {
int listSize = readToolList.size();
int listStart = 0, listEnd = 0;
int ropeNum = listSize/2000;
/**
* 假设101条数据每次40,总共循环101/4=2
* 0<=X<40 i=0 40*i 40*i+40
* 40<=X<80 i=1 40*i 40*i+40
* 80<=X<101 40*i+40 101
*/
for(int i = 0 ; i < ropeNum; i++) {
//数据入库操作
listStart = i *2000;
listEnd = i * 2000 +2000;
writeToolService.createBomDetail(readToolList.subList(listStart, listEnd));
}
writeToolService.createBomDetail(readToolList.subList(listEnd, listSize));
} catch (Exception e) {
//记录出现异常的时间,线程name
result = new AsyncResult ("fail,time=" + System.currentTimeMillis() + ",thread id=" + Thread.currentThread().getName() + ",pageIndex=" + pageIndex);
}
}
return result;
}
}
创建线程分批传入Future:
@Service
public class ToolsThread {
private static final Logger LOG = LoggerFactory.getLogger(ToolsThread.class);
@Autowired
private SyncToolsHandler syncToolsHandler;
@Autowired
ReadToolsService readToolsService;
// 核心线程数
@Value("${book.core.poolsize}")
private int threadSum;
public void receiveBookJobRun() {
List readToolModels = new ArrayList();
readToolModels = readToolsService.getToolListByAutoId(0, 5000000);
// 入库开始时间
Long inserOrUpdateBegin = System.currentTimeMillis();
LOG.info("数据更新开始时间:" + inserOrUpdateBegin);
// 接收集合各段的 执行的返回结果
List> futureList = new ArrayList>();
// 集合总条数
if (readToolModels != null) {
// 将集合切分的段数(2*CPU的核心数)
int threadSum = 2 * Runtime.getRuntime().availableProcessors();
int listSize = readToolModels.size();
int listStart, listEnd;
// 当总条数不足threadSum条时 用总条数 当做线程切分值
if (threadSum > listSize) {
threadSum = listSize;
}
// 将list 切分多份 多线程执行
for (int i = 0; i < threadSum; i++) {
// 计算切割 开始和结束
listStart = listSize / threadSum * i;
listEnd = listSize / threadSum * (i + 1);
// 最后一段线程会 出现与其他线程不等的情况
if (i == threadSum - 1) {
listEnd = listSize;
}
// 数据切断
List readToolList= readToolModels.subList(listStart, listEnd);
// 每段数据集合并行入库
futureList.add(syncToolsHandler.syncTools(readToolList, i));
}
// 对各个线程段结果进行解析
for (Future future : futureList) {
String str;
if (null != future) {
try {
str = future.get().toString();
LOG.info("current thread id =" + Thread.currentThread().getName() + ",result=" + str);
} catch (ExecutionException | InterruptedException e) {
LOG.info("线程运行异常!");
}
} else {
LOG.info("线程运行异常!");
}
}
}
Long inserOrUpdateEnd = System.currentTimeMillis();
LOG.info("数据更新结束时间:" + inserOrUpdateEnd + "。此次更新数据花费时间为:" + (inserOrUpdateEnd - inserOrUpdateBegin));
}
}
同步时间大概15分钟
2020-08-08 16:58:29 [main] INFO com.commons.service.sync.ToolsThread -数据更新结束时间:1596877109637。此次更新数据花费时间为:990284