使用 CompletableFuture 异步组装数据

一种快捷、优雅的异步组装数据方式

实际项目中经常遇到这种情况: 从多个表中查找到数据然后拼装成一个VO返回给前端。

这个过程有可能会非常耗时。因为最终每一条返回的VO数据是由多个表中的数据拼装而成,如果项目还是微服务需要从其他服务获取数据,那将会更加耗时,更加麻烦。简单的几十条、几百条数据单个线程跑起来可能没有什么压力,但是当数量达到成千上万,几十万,几百万,组装的逻辑也变得非常复杂时,这个操作就非常耗时。

最近我在项目中就遇到这个的情况。项目中我们需要做一个相关流程数据的下载功能。

最初版本使用单线程,因为业务的复杂性,5000多条数据完全下载下来需要30min。以为是从数据库分拣数据比较耗时,查询日志后发现数据库查询并没有耗时多久,反而是组装数据占用了大多数时间。

因此机智的我就想起之前同组小伙伴分享的Java8一个新的类CompletableFuture。

CompletableFuture 简介

CompletableFuture 是Java 8 新增加的Api,该类实现,Future和CompletionStage两个接口,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。

具体大家可以查看Java Api 文档,或者阅读网上一些博客。

CompletableFuture 异步组装数据

代码示例如下

/**
* 功能描述: 拼装数据
* @author lkb
* @date 2019/12/25
* @param
* @return java.util.List<com.laidian.erp.crm.vo.DeviceProcessListExportVO>
*/
private List<DeviceProcessListExportVO> listByFlowJobIds(List<String> flowJobIds, Map<String, ProcessInfoVo> map, Map<Integer,UserInfoDTO> userInfoDTOMap, Map<Integer,HatCity> cityMap){
//result 列表保存组装完成的数据
List<DeviceProcessListExportVO> result = new LinkedList<>();
//每次组装100条数据
List<List<String>> partition = Lists.partition(flowJobIds,100);
List<CompletableFuture> futures = partition.stream().map(subList -> CompletableFuture.supplyAsync(() -> {
//packVOs 方法就是组装数据
return packVOs(subList,map,userInfoDTOMap,cityMap);
},ASYNC_IO_POOL).whenCompleteAsync((r,e)->result.addAll(r))
.exceptionally(e->{
log.error(e.getMessage(),e);
log.error("listByFlowJobIds error.");
return result;
})).collect(Collectors.toList()); CompletableFuture<Void> all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
log.info("任务阻塞 ");
Instant start = Instant.now();
//阻塞,直到所有任务结束。
all.join();
log.info("任务阻塞结束 耗时 = {}",ChronoUnit.MILLIS.between(start, Instant.now()));
return result;
}

具体步骤如下:

  1. 将原始数据按照每组100条进行拆分。(具体每组拆分多少条需要根据实际的业务情况和服务器性能,多测试一下应该就知道了)
  2. 多线程组成数据,每个线程组装一组数据(上面拆分的100条原始数据)。packVOs 方法就是组装数据。为了高效,我建议 在组装数据的时候多采用批量,缓存的思想,能批量尽量批量,重复数据就尽量缓存下来。
  3. CompletableFuture.supplyAsync() 方法说明如下。第一个参数是线程需要执行的动作,第二个参数是线程执行用的Executor,可以填自定义的,也可以不填写,不填写程序会使用默认的执行器。

public static CompletableFuture supplyAsync(Supplier supplier, Executor executor)

返回由给定执行程序中运行的任务异步完成的新CompletableFuture,其中包含通过调用给定供应商获得的值。

  1. whenCompleteAsync 方法含义和名字一样,将上一步执行的结果或者异常作为参数传给指定的参数。这里我们希望分批组装的结果能过add进result中。
  2. exceptionally 是用来处理异常。当一个线程执行出现异常的时候应该执行怎样的操作。
  3. all.join() 这个方法是等待所有的任务(所有的CompletableFuture)完成。组装数据是耗时的,如果我们不等待所有组装任务完成,直接返回result,相信result中不会有数据,或者数据是不完整的。我们期待的结果是所有的数据都正常组装完成,添加进result。

使用了CompletableFuture方式实现多线程分批组装,并且在组装时采用 “批量+缓存” 的思想,原来5000条数据30min缩短为3min。当然还有优化的空间,但是能达到这个效果已经让我非常满意了。

下次遇到类似的情况,我会优先考虑CompletableFuture分批组装的方式,快捷、优雅。你们有好的方法呢?

使用 CompletableFuture 异步组装数据的更多相关文章

  1. ASP模拟POST请求异步提交数据的方法

    这篇文章主要介绍了ASP模拟POST请求异步提交数据的方法,本文使用MSXML2.SERVERXMLHTTP.3.0实现POST请求,需要的朋友可以参考下 有时需要获取远程网站的某些信息,而服务器又限 ...

  2. flask+sqlite3+echarts3+ajax 异步更新数据

    结构: /www | |-- /static |....|-- jquery-3.1.1.js |....|-- echarts.js(echarts3是单文件!!) | |-- /templates ...

  3. 串行通讯之.NET SerialPort异步写数据

    目录 第1章说明    2 1 为什么需要异步写数据?    2 2 异步写数据的代码    2 3 源代码    4 第1章说明 1 为什么需要异步写数据? 如下图所示,以波特率300打开一个串口. ...

  4. 使用load()方法异步请求数据

    使用load()方法通过Ajax请求加载服务器中的数据,并把返回的数据放置到指定的元素中,它的调用格式为: load(url,[data],[callback]) 参数url为加载服务器地址,可选项d ...

  5. C# 实现的多线程异步Socket数据包接收器框架

    转载自Csdn : http://blog.csdn.net/jubao_liang/article/details/4005438 几天前在博问中看到一个C# Socket问题,就想到笔者2004年 ...

  6. nettyclient异步获取数据

    源代码见,以下主要是做个重要代码记录 http://download.csdn.net/detail/json20080301/8180351 NETTYclient获取数据採用的方式是异步获取数据, ...

  7. jQuery选取所有复选框被选中的值并用Ajax异步提交数据

    昨天和朋友做一个后台管理系统项目的时候涉及到复选框批量操作,如果用submit表单提交挺方便的,但是要实现用jQuery结合Ajax异步提交数据就有点麻烦了,因为我之前做过的项目中基本上没用Ajax来 ...

  8. echarts异步数据加载(在下拉框选择事件中异步更新数据)

    接触echarts 大半年了,从不会到熟练也做过不少的图表,隔了一段时间没使用这玩意,好多东西真心容易忘了.在接触echarts这期间也没有总结什么东西,今天我就来总结一下如何在echart中异步加载 ...

  9. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二九║ Nuxt实战:异步实现数据双端渲染

    回顾 哈喽大家好!又是元气满满的周~~~二哈哈,不知道大家中秋节过的如何,马上又是国庆节了,博主我将通过三天的时间,给大家把项目二的数据添上(这里强调下,填充数据不是最重要的,最重要的是要配合着让大家 ...

随机推荐

  1. Android APP开发内容图片不显示

    I/Glide: Root cause (1 of 1) Cause (1 of 1): class java.io.FileNotFoundException: No content provide ...

  2. python写冒泡排序

    冒泡就是重复地遍历要排序的数列,一次比较两个元素(泡泡),如果他们的顺序错误就把他们交换过来,像泡泡一样,依次按照顺序上升排列. 冒泡排序算法的运作如下: 比较相邻的元素.如果第一个比第二个大(升序) ...

  3. Ambari+HDP+HDF离线安装包下载清单

    Ambari 2.7.3 Repositories OS Format URL RedHat 7 CentOS 7 Oracle Linux 7 Base URL http://public-repo ...

  4. JWT实现分布式Session

    JWT是什么 JWT一看就是简称,它的全称JSON Web Token,从字面上我们看出 1.数据是JSON格式 2.用于Web应用 3.是一个Token,也就是一个令牌方式 看看官方的说明,它定义了 ...

  5. 【题解】P5589 小猪佩奇玩游戏(期望)

    [题解]P5589 小猪佩奇玩游戏(期望) 假设一个点有\(x\)个点(包括自己)可以到达他,他就对答案有\(1/x\)的贡献.这是因为这个点必须被删掉而通过删掉这个点本身删掉这个点的概率是\(1/x ...

  6. 洛谷$P5329\ [SNOI2019]$字符串 字符串

    正解:字符串 解题报告: 传送门$QwQ$ 有两个很妙的方法,分别港下$QwQ$ 首先为了表示方便,这里和题面一样设$s_i$表示去掉第$i$个字母得到的字符串.另设$lcp(i,j)$表示$suf_ ...

  7. 基于 HTML5 WebGL 与 WebVR 3D 虚实现实的可视化培训系统

    前言 2019 年 VR, AR, XR, 5G, 工业互联网等名词频繁出现在我们的视野中,信息的分享与虚实的结合已经成为大势所趋,5G 是新一代信息通信技术升级的重要方向,工业互联网是制造业转型升级 ...

  8. Go 每日一库之 go-flags

    简介 在上一篇文章中,我们介绍了flag库.flag库是用于解析命令行选项的.但是flag有几个缺点: 不显示支持短选项.当然上一篇文章中也提到过可以通过将两个选项共享同一个变量迂回实现,但写起来比较 ...

  9. Linux普通用户如何获取root权限 sudo -i

    从下图中可以知道,xiaolai这个用户连接到服务器后它没有权限,更新代码都更新不了 那么我们作为一个普通用户要获取管理员权限,怎么办呢? 输入 sudo -i 然后再输入普通用户的这个密码 获取成功 ...

  10. Java 利用Map集合计算一个字符串中每个字符出现的次数

    步骤分析 1.给出一串字符串,字符串中可以包含字母.数字.符号等等. 2.创建一个Map集合,key是字符串中的字符,value是字符的个数. 3.遍历字符串,获取每一个字符. 5.使用获取到的字符, ...