前言:
通常我们都会遇到分页拉取的需求,比如与第三方系统同步数据,定时拉取全量数据做缓存,下面我们简单介绍下多线程分页写法
需求:
全量同步第三方系统数据,并在全部数据同步完后,统一做缓存数据处理
前置条件:
1.使用springBoot的ThreadPoolTaskExecutor 多线程封装数据
2.springBoot2.x、jdk8
完成步骤:
1.计算对应页数,把每页需求
2.循环多线程拉取
3.多线程内的子线程都完成了之后,再统一做缓存
具体代码:
1.线程池taskExecutor对象配置
@Configuration
@Data
public class ExecutorConfig {
@Value("${pull.executor.corePoolSize:4}")
public Integer corePoolSize;
@Value("${pull.executor.maxPoolSize:20}")
public Integer maxPoolSize;
@Value("${pull.executor.queueCapacity:1000}")
public Integer queueCapacity;
@Value("${pull.executor.keepAliveSeconds:30000}")
public Integer keepAliveSeconds;
/**
* 以下corePoolSize,maxPoolSize
* 按照io密集与cpu密集来定,如果是网络连接io,文件读写io,中间件(mysql,redis,mq)交互io等场景则为io密集
* 如果是数据计算,数据排序等纯内存计算等场景则为cpu密集
* n表示实际cpu核数
* io密集:2n+1
* cpu密集:n+1
*
* 实际应用中我们不会硬套上述公式,(部分机器会有4核8线程,也就是超线程技术,这些可以自行了解)
* 而是会根据服务实例(现在多是容器化部署),压测结果,实际业务场景来确定最终的corePoolSize,maxPoolSize
* 一般是根据经验值设置初始值,先做一轮压测,根据压测结果定具体参数
* @return ThreadPoolTaskExecutor ThreadPoolTaskExecutor
*/
@Bean(name = "taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
//线程池维护线程的核心线程数
poolTaskExecutor.setCorePoolSize(corePoolSize);
//线程池维护线程的最大数量
poolTaskExecutor.setMaxPoolSize(maxPoolSize);
//线程池所使用的缓冲队列
poolTaskExecutor.setQueueCapacity(queueCapacity);
//线程池维护线程所允许的空闲时间
poolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
//线程命名前缀
poolTaskExecutor.setThreadNamePrefix("CUSTOM-GOODS");
//设置拒绝策略
poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return poolTaskExecutor;
}
}
2.多线程+CountDownLatch
public void doRepetition() {
log.info("处理重复组合编码 start");
doneService.initRingCode();
List<DoneRingPO> doneRingList = doneRingMapper.queryRepetitionRing();
if (CollectionUtils.isEmpty(doneRingList)) {
log.info("处理重复组合编码为空,处理重复组合编码 end");
return;
}
for (DoneRingPO item : doneRingList) {
List<DoneRingPO> repetitionCodeList = doneRingMapper.queryDoneRingByCode(Collections.singletonList(String.valueOf(item.getCode())));
for (int i = 1; i < repetitionCodeList.size(); i++) {
repetitionCodeList.get(i).setCode(redisTemplate.opsForValue().increment(DoneCacheKey.DONE_RING_CODE_INCR.getKey()));
}
for (DoneRingPO ringPo : repetitionCodeList) {
doneRingMapper.updateRingCode(ringPo);
}
}
log.info("处理重复组合编码 end");
}
3.多线程+CompletableFuture
public void testMulti(Integer totalNum) throws Exception {
int totalPage = totalNum;
final List<CompletableFuture<String>> futureList = new ArrayList<>();
for (int i = 1; i <= totalPage; i++) {
try {
final int page =i;
futureList.add(CompletableFuture.supplyAsync(() -> {
//TODO 执行耗时任务
log.info("耗时任务page={}",page);
return Integer.toString(page);
}, taskExecutor));
} catch (Exception e) {
log.error("线程异常....", e);
}
}
final CompletableFuture<Void> allOf = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]));
//线程等待完成
allOf.get();
//TODO 执行缓存任务
log.info("结束");
}