项目场景二:使用线程池+分页查询大数据并导出

场景分析

接口需求:将分页查询的治疗记录列表数据进行导出
业务逻辑:根据搜索条件和登录人员权限进行分页查询(治疗操作记录表中治疗小项目数据是单独的一张表需要联表查询)再将查询到的数据导出

优化

如果一次数据库查询太多数据,jvm就会内存溢出,所以我使用分页每次查询2万条数据,使用for循环,再把结果合并到一起。但是这种串行方式是阻塞的,查询20w左右数据大约需要15秒,然后就通过线程池用多线程查询,用时3秒多。

      @Override
    public List<CureListVo> exportCureList(CureListRo ro) {
        List<List<Long>> subLists = new ArrayList<>();
        // 数据大小
        List<Long> ids = buSignatureMapper.selectSignatureCount(ro);
        // 批次大小(每个线程要处理数据量)
        int batchSize = 20000; // 每个子列表的大小为2万
        // 分割List为多个子列表
        for (int i = 0; i < ids.size(); i += batchSize) {
            int endIndex = Math.min(i + batchSize, ids.size());
            List<Long> subList = ids.subList(i, endIndex);
            subLists.add(subList);
        }
        Queue<CureListVo> results = new ConcurrentLinkedQueue<>();
        if (ObjectUtil.isNotEmpty(subLists)) {
            // 使用定长线程池,大小根据实际情况调整,例如数据库连接数、CPU核心数等
            int poolSize = Math.min(subLists.size(), Runtime.getRuntime().availableProcessors());
            ExecutorService executor = Executors.newFixedThreadPool(poolSize);
            CountDownLatch latch = new CountDownLatch(subLists.size());
            for (List<Long> subList : subLists) {
                CureListRo ro1 = new CureListRo();
                BeanUtil.copyProperties(ro,ro1);
                ro1.setStartIndex(subList.get(0));
                ro1.setEndIndex(subList.get(subList.size()-1));
                executor.submit(() -> {
                    try {
                        // 查询MySQL表操作
                        List<CureListVo> cureListVos1 = buSignatureMapper
                                .selectCureList(ro1);
                        // 直接添加到ConcurrentLinkedQueue中,它是线程安全的
                        System.out.println(ro1.getStartIndex()+"到"+ro1.getEndIndex()+"total"+cureListVos1.size());
                        results.addAll(cureListVos1);
                    } finally {
                        // 计数器减一,表示一个任务完成
                        latch.countDown();
                    }
                });
            }
            // 阻塞等待所有查询任务完成
            try {
                latch.await();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("Thread pool interrupted while awaiting termination: {}", e.getMessage());
            } finally {
                // 关闭线程池
                executor.shutdown();
            }
        }
        List<CureListVo> cureListVos = results.stream().sorted(Comparator.comparing(CureListVo::getCureEndTime).reversed()).collect(Collectors.toList());
        if(StringUtils.isNotBlank(ro.getSytchName()))
        {
            cureListVos = cureListVos.stream().filter(cureListVo -> cureListVo.getItems().contains(ro.getSytchName()))
                    .collect(Collectors.toList());
        }
        return cureListVos;
    }
  

改进

不推荐使用Executors来创建线程池内置参数不合理容易造成OOM

 
  @Override
    public List<CureListVo> exportCureList(CureListRo ro) {
        List<List<Long>> subLists = new ArrayList<>();
        // 数据大小
        List<Long> ids = buSignatureMapper.selectSignatureCount(ro);
        // 批次大小(每个线程要处理数据量)
        int batchSize = 20000; // 每个子列表的大小为2万
        // 分割List为多个子列表
        for (int i = 0; i < ids.size(); i += batchSize) {
            int endIndex = Math.min(i + batchSize, ids.size());
            List<Long> subList = ids.subList(i, endIndex);
            subLists.add(subList);
        }
        Queue<CureListVo> results = new ConcurrentLinkedQueue<>();
        if (ObjectUtil.isNotEmpty(subLists)) {
            // 使用自定义程池,大小根据实际情况调整,例如数据库连接数、CPU核心数等
            int poolSize = Math.min(subLists.size(), Runtime.getRuntime().availableProcessors());
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(                   
						poolSize,   poolSize,  1,   TimeUnit.HOURS,   
						new SynchronousQueue<Runnable>(),       
						new ThreadPoolExecutor.AbortPolicy()    
						);
            CountDownLatch latch = new CountDownLatch(subLists.size());
            for (List<Long> subList : subLists) {
                CureListRo ro1 = new CureListRo();
                BeanUtil.copyProperties(ro,ro1);
                ro1.setStartIndex(subList.get(0));
                ro1.setEndIndex(subList.get(subList.size()-1));
                executor.submit(() -> {
                    try {
                        // 查询MySQL表操作
                        List<CureListVo> cureListVos1 = buSignatureMapper
                                .selectCureList(ro1);
                        // 直接添加到ConcurrentLinkedQueue中,它是线程安全的
                        System.out.println(ro1.getStartIndex()+"到"+ro1.getEndIndex()+"total"+cureListVos1.size());
                        results.addAll(cureListVos1);
                    } finally {
                        // 计数器减一,表示一个任务完成
                        latch.countDown();
                    }
                });
            }
            // 阻塞等待所有查询任务完成
            try {
                latch.await();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("Thread pool interrupted while awaiting termination: {}", e.getMessage());
            } finally {
                // 关闭线程池
                executor.shutdown();
            }
        }
        List<CureListVo> cureListVos = results.stream().sorted(Comparator.comparing(CureListVo::getCureEndTime).reversed()).collect(Collectors.toList());
        if(StringUtils.isNotBlank(ro.getSytchName()))
        {
            cureListVos = cureListVos.stream().filter(cureListVo -> cureListVo.getItems().contains(ro.getSytchName()))
                    .collect(Collectors.toList());
        }
        return cureListVos;
    }
  

知识回顾

Future回顾

新方案(未完成)


public class CompletableFutureTests {

    @Autowired
    private UserDao userDao;

    @Test
    public void testSomeTaskAndJoin() throws Exception {

        // DynamicDataSourceContextHolder.dataSourceIds根据动态数据源数量
        // 异步执行每个数据源查询方法
        // 返回一个Future集合
        List<CompletableFuture<List<User>>> futures = DynamicDataSourceContextHolder.dataSourceIds.stream()
                .map(this::queryUsers).collect(Collectors.toList());

        // 多个异步执行结果合并到该集合
        List<User> futureUsers = new ArrayList<>();

        // 通过allOf对多个异步执行结果进行处理
        CompletableFuture allFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
                                .whenComplete((v, t) -> {
                                    // 所有CompletableFuture执行完成后进行遍历
                                    futures.forEach(future -> {
                                        synchronized (this) {
                                            // 查询结果合并
                                            futureUsers.addAll(future.getNow(null));
                                        }
                                    });
                                });


        // 阻塞等待所有CompletableFuture执行完成
        allFuture.get();
        // 对合并后的结果集进行去重处理
        List<User> result = futureUsers.stream().distinct().collect(Collectors.toList());

        log.info(result.toString());

    }

    /**
     * 用户异步查询方法
     * @param datasourceKey 动态数据源Key
     * @return
     */
    public CompletableFuture<List<>> queryUsers(String datasourceKey) {

        // 定义异步查询Future对象
        CompletableFuture<List<Long>> queryFuture = CompletableFuture.supplyAsync(() -> {

            return userDao.selectAll();
        });

        // 异步完成执行方法
        queryFuture.whenCompleteAsync(new BiConsumer<List<User>, Throwable>() {
            @Override
            public void accept(List<User> users, Throwable throwable) {
                // 这里主要记录异步执行结果
                log.info("数据源[{}]查询完成,查询记录[{}]条", datasourceKey, users.size());
            }
        });

        // 返回future对象
        return queryFuture;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值