【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。
前言
工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回。
还可能在微服务中遇到类似的情况,某个服务的接口,要使用好几次feign去调用其他服务的方法获取数据,最后拿到想要的值并封装返回给前端。
这样的场景下,当某个或多个rpc调用的方法比较耗时,整个接口的响应就会非常慢。Java8之后,有一个工具非常适合处理这种场景,就是CompletableFuture。
场景
本章主要讲解CompletableFuture的并行处理用法,来针对这种很常见的场景,帮助大家快速掌握并应用到实际工作当中。CompletableFuture内部的用法还有许多,但个人用到的场景大多都是并行处理,对其他场景感兴趣的小伙伴可以另行百度搜索。
场景说明:
写一个接口,调用另外两个HTTP接口,分别获取二十四节气和星座,最后放在一起返回。
用法
1、在线API
我们访问极速数据网站https://www.jisuapi.com ,注册一个账号,就可以免费使用里面的一些在线API,平均每天有100次免费机会,对于我这样经常本地做一些测试的人来说完全够用了。
这里,我使用了其中的查询二十四节气API,和查询星座API,后面会提供案例代码,也可以直接使用我的。
2、编写在线API查询
这里,我们在查询时,模拟耗时的情况。
1)、查询二十四节气
package com.example.async.service;
import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* <p>
* 查询二十四节气的服务
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-26 15:25
*/
@Service
@Slf4j
public class TwentyFourService {
public static final String APPKEY = "xxxxxx";// 你的appkey
public static final String URL = "https://api.jisuapi.com/jieqi/query";
public String getResult() {
String url = URL + "?appkey=" + APPKEY;
String result = HttpUtil.get(url);
// 模拟耗时
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
log.error("[二十四节气]>>>> 异常: {}", e.getMessage(), e);
}
return result;
}
}
2)、查询星座
package com.example.async.service;
import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 查询星座的服务
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-26 15:25
*/
@Service
@Slf4j
public class ConstellationService {
public static final String APPKEY = "xxxxxx";// 你的appkey
public static final String URL = "https://api.jisuapi.com/astro/all";
public String getResult() {
String url = URL + "?appkey=" + APPKEY;
String result = HttpUtil.get(url);
// 模拟耗时
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
log.error("[星座]>>>> 异常: {}", e.getMessage(), e);
}
return result;
}
}
3、编写查询服务
package com.example.async.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 查询服务
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-26 17:38
*/
@Service
@Slf4j
public class QueryService {
private final TwentyFourService twentyFourService;
private final ConstellationService constellationService;
public QueryService(TwentyFourService twentyFourService, ConstellationService constellationService) {
this.twentyFourService = twentyFourService;
this.constellationService = constellationService;
}
/**
* 同步返回结果
* @return 结果
*/
public Map<String, Object> query() {
// 1、查询二十四节气
String twentyFourResult = twentyFourService.getResult();
// 2、查询星座
String constellationResult = constellationService.getResult();
// 3、返回
Map<String, Object> map = new HashMap<>();
map.put("twentyFourResult", twentyFourResult);
map.put("constellationResult", constellationResult);
return map;
}
}
4、编写测试接口
这里,我们专门加上了耗时计算。
package com.example.async.controller;
import cn.hutool.core.date.TimeInterval;
import com.example.async.service.QueryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* <p>
* 测试
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-26 17:35
*/
@RestController
@RequestMapping("/api")
@Slf4j
public class TestController {
private final QueryService queryService;
public TestController(QueryService queryService) {
this.queryService = queryService;
}
/**
* 同步查询
* @return 结果
*/
@GetMapping("/query")
public ResponseEntity<Map<String, Object>> query() {
// 计时
final TimeInterval timer = new TimeInterval();
timer.start();
Map<String, Object> map = queryService.query();
map.put("costTime", timer.intervalMs() + " ms");
return ResponseEntity.ok().body(map);
}
}
5、效果
可以看到,两个接口一共耗费了10秒左右才返回。
6、CompletableFuture并行查询
现在我们来使用CompletableFuture改造下接口,并行查询两个HTTP接口再返回。
package com.example.async.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* <p>
* 查询服务
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-26 17:38
*/
@Service
@Slf4j
public class QueryService {
private final TwentyFourService twentyFourService;
private final ConstellationService constellationService;
public QueryService(TwentyFourService twentyFourService, ConstellationService constellationService) {
this.twentyFourService = twentyFourService;
this.constellationService = constellationService;
}
/**
* 异步返回结果
* @return 结果
*/
public Map<String, Object> queryAsync() {
Map<String, Object> map = new HashMap<>();
// 1、查询二十四节气
CompletableFuture<String> twentyFourQuery = CompletableFuture.supplyAsync(twentyFourService::getResult);
twentyFourQuery.thenAccept((result) -> {
log.info("查询二十四节气结果:{}", result);
map.put("twentyFourResult", result);
}).exceptionally((e) -> {
log.error("查询二十四节气异常: {}", e.getMessage(), e);
map.put("twentyFourResult", "");
return null;
});
// 2、查询星座
CompletableFuture<String> constellationQuery = CompletableFuture.supplyAsync(constellationService::getResult);
constellationQuery.thenAccept((result) -> {
log.info("查询星座结果:{}", result);
map.put("constellationResult", result);
}).exceptionally((e) -> {
log.error("查询星座异常: {}", e.getMessage(), e);
map.put("constellationResult", "");
return null;
});
// 3、allOf-两个查询必须都完成
CompletableFuture<Void> allQuery = CompletableFuture.allOf(twentyFourQuery, constellationQuery);
CompletableFuture<Map<String, Object>> future = allQuery.thenApply((result) -> {
log.info("------------------ 全部查询都完成 ------------------ ");
return map;
}).exceptionally((e) -> {
log.error(e.getMessage(), e);
return null;
});
// 获取异步方法返回值
// get()-内部抛出了异常需手动处理; join()-内部处理了异常无需手动处理,点进去一看便知。
future.join();
return map;
}
}
7、编写测试接口
package com.example.async.controller;
import cn.hutool.core.date.TimeInterval;
import com.example.async.service.QueryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* <p>
* 测试
* </p>
*
* @author 福隆苑居士,公众号:【Java分享客栈】
* @since 2022-04-26 17:35
*/
@RestController
@RequestMapping("/api")
@Slf4j
public class TestController {
private final QueryService queryService;
public TestController(QueryService queryService) {
this.queryService = queryService;
}
/**
* 异步查询
* @return 结果
*/
@GetMapping("/queryAsync")
public ResponseEntity<Map<String, Object>> queryAsync() {
// 计时
final TimeInterval timer = new TimeInterval();
timer.start();
Map<String, Object> map = queryService.queryAsync();
map.put("costTime", timer.intervalMs() + " ms");
return ResponseEntity.ok().body(map);
}
}
8、CompletableFuture效果
可以看到,时间缩短了一倍。
9、思考
如果在微服务中,有一个很复杂的业务需要远程调用5个第三方laji厂家的接口,每个接口假设都耗时5秒,使用CompletableFuture并行处理最终需要多久?
答案是肯定的,同步查询需要25秒左右,CompletableFuture并行处理还是5秒左右,也就是说,同一个接口中,调用的耗时接口越多,CompletableFuture优化的幅度就越大。
示例代码
可以下载我的完整示例代码本地按需测试,里面有我的极速数据API的key,省得自己注册账号了,每天免费就100次,先到先得哦。
链接:https://pan.baidu.com/doc/share/P_Jn_x22fos0ED3YEnqI8A-232386145447394
提取码:piil
觉得有帮助的话,就请顺手点个【推荐】吧,本人定期分享工作中的经验及趣事,原创文章均为手打,喜欢的话也可以关注一下哦~
【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。的更多相关文章
- 【Java分享客栈】一文搞定京东零售开源的AsyncTool,彻底解决异步编排问题。
一.前言 本章主要是承接上一篇讲CompletableFuture的文章,想了解的可以先去看看案例: https://juejin.cn/post/7091132240574283813 Comple ...
- 一文搞定 SonarQube 接入 C#(.NET) 代码质量分析
1. 前言 C#语言接入Sonar代码静态扫描相较于Java.Python来说,相对麻烦一些.Sonar检测C#代码时需要预先编译,而且C#代码必须用MSbuid进行编译,如果需要使用SonarQub ...
- 【Java分享客栈】我为什么极力推荐XXL-JOB作为中小厂的分布式任务调度平台
前言 大家好,我是福隆苑居士,今天给大家聊聊XXL-JOB的使用. XXL-JOB是本人呆过的三家公司都使用到的分布式任务调度平台,前两家都是服务于传统行业(某大型移动基地和某大型电网),现在 ...
- 【Java分享客栈】SpringBoot线程池参数搜一堆资料还是不会配,我花一天测试换你此生明白。
一.前言 首先说一句,如果比较忙顺路点进来的,可以先收藏,有时间或用到了再看也行: 我相信很多人会有一个困惑,这个困惑和我之前一样,就是线程池这个玩意儿,感觉很高大上,用起来很fashion, ...
- 【Java分享客栈】SpringBoot整合WebSocket+Stomp搭建群聊项目
前言 前两周经常有大学生小伙伴私信给我,问我可否有偿提供毕设帮助,我说暂时没有这个打算,因为工作实在太忙,现阶段无法投入到这样的领域内,其中有两个小伙伴又问到我websocket该怎么使用,想给自己的 ...
- 【Java分享客栈】超简洁SpringBoot使用AOP统一日志管理-纯干货干到便秘
前言 请问今天您便秘了吗?程序员坐久了真的会便秘哦,如果偶然点进了这篇小干货,就麻烦您喝杯水然后去趟厕所一边用左手托起对准嘘嘘,一边用右手滑动手机看完本篇吧. 实现 本篇AOP统一日志管理写法来源于国 ...
- Java性能调优攻略全分享,5步搞定!(附超全技能图谱)
对于很多研发人员来说,Java 性能调优都是很头疼的问题,为什么这么说?如今,一个简单的系统就囊括了应用程序.数据库.容器.操作系统.网络等技术,线上一旦出现性能问题,就可能要你协调多方面组件去进行优 ...
- 搞定 CompletableFuture,并发异步编程和编写串行程序还有什么区别?你们要的多图长文
你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...
- 一文搞定MySQL的事务和隔离级别
一.事务简介 事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成. 一个数据库事务通常包含了一个序列的对数据库的读/写操作.它的存在包含有以下两个目的: 为数据库操作序列提供 ...
随机推荐
- docker容器登录,退出等操作命令
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口 ...
- v-for key值?
不写key值会报warning, 和react的array渲染类似. 根据diff算法, 修改数组后, 写key值会复用, 不写会重新生成, 造成性能浪费或某些不必要的错误
- yarn上运行flink环境搭建
主要完成hadoop集群搭建和yarn上运行flink 1.搭建hadoop伪集群 主要是搭建hadoop MapReduce(yarn)和HDFS 1.1 下载&配置环境变量 这里下载的ha ...
- 如何在自定义端口上运行 Spring Boot 应用程序?
为了在自定义端口上运行 Spring Boot 应用程序,您可以在 application.properties 中指定端口. server.port = 8090
- ArrayList、LinkedList、Vector、Array
ArrayList 本质是一个数组. 优势:追加元素到数组末尾的时候速度快,同时检索元素的速度也快. 劣势:如果要插入一个元素到数组之间慢:如果要追加的元素数量多于数组的容量,则需要频繁扩容使用Arr ...
- openldap 资料
LDAP概念和原理介绍 相信对于许多的朋友来说,可能听说过LDAP,但是实际中对LDAP的了解和具体的原理可能还比较模糊,今天就从"什么是LDAP"."LDAP的主要产品 ...
- isNotEmpty 与 isNotBlank 的区别
isNotEmpty(str)等价于 str != null && str.length > 0 isNotBlank(str) 等价于 str != null &&am ...
- Web存储之LocalStorage初探
Web存储之LocalStorage初探 HTML5的发布和定稿为前端界带来巨大的变化,新增的API和特性给业务带来了更多可能性,让用户体验拥有了更可能的丰富. · HTML Geolocation ...
- API的自动化测试
传统的测试工具在测试一个API的时候,必须手动填写这个API所需要接收的所有信息,比如一个查询航班动态的API,他接收两个输入字段,一个叫flight, 一个叫date,那么测试这个API的用户,需要 ...
- JavaScript & 6小时了解ES6基本语法
一步一步似爪牙. 前言 学习es6之前我们可能并不知道es6相比es5差距在哪, 但是这并不妨碍我们站在巨人的肩膀上; 程序员就是要乐于尝鲜; 学习es6最终目的是结合es5 一起进行工程项目开发, ...