前言

  工作中你可能会遇到很多这样的场景,一个接口,要从其他几个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并行处理,成倍缩短查询时间。的更多相关文章

  1. 【Java分享客栈】一文搞定京东零售开源的AsyncTool,彻底解决异步编排问题。

    一.前言 本章主要是承接上一篇讲CompletableFuture的文章,想了解的可以先去看看案例: https://juejin.cn/post/7091132240574283813 Comple ...

  2. 一文搞定 SonarQube 接入 C#(.NET) 代码质量分析

    1. 前言 C#语言接入Sonar代码静态扫描相较于Java.Python来说,相对麻烦一些.Sonar检测C#代码时需要预先编译,而且C#代码必须用MSbuid进行编译,如果需要使用SonarQub ...

  3. 【Java分享客栈】我为什么极力推荐XXL-JOB作为中小厂的分布式任务调度平台

    前言   大家好,我是福隆苑居士,今天给大家聊聊XXL-JOB的使用.   XXL-JOB是本人呆过的三家公司都使用到的分布式任务调度平台,前两家都是服务于传统行业(某大型移动基地和某大型电网),现在 ...

  4. 【Java分享客栈】SpringBoot线程池参数搜一堆资料还是不会配,我花一天测试换你此生明白。

    一.前言   首先说一句,如果比较忙顺路点进来的,可以先收藏,有时间或用到了再看也行:   我相信很多人会有一个困惑,这个困惑和我之前一样,就是线程池这个玩意儿,感觉很高大上,用起来很fashion, ...

  5. 【Java分享客栈】SpringBoot整合WebSocket+Stomp搭建群聊项目

    前言 前两周经常有大学生小伙伴私信给我,问我可否有偿提供毕设帮助,我说暂时没有这个打算,因为工作实在太忙,现阶段无法投入到这样的领域内,其中有两个小伙伴又问到我websocket该怎么使用,想给自己的 ...

  6. 【Java分享客栈】超简洁SpringBoot使用AOP统一日志管理-纯干货干到便秘

    前言 请问今天您便秘了吗?程序员坐久了真的会便秘哦,如果偶然点进了这篇小干货,就麻烦您喝杯水然后去趟厕所一边用左手托起对准嘘嘘,一边用右手滑动手机看完本篇吧. 实现 本篇AOP统一日志管理写法来源于国 ...

  7. Java性能调优攻略全分享,5步搞定!(附超全技能图谱)

    对于很多研发人员来说,Java 性能调优都是很头疼的问题,为什么这么说?如今,一个简单的系统就囊括了应用程序.数据库.容器.操作系统.网络等技术,线上一旦出现性能问题,就可能要你协调多方面组件去进行优 ...

  8. 搞定 CompletableFuture,并发异步编程和编写串行程序还有什么区别?你们要的多图长文

    你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...

  9. 一文搞定MySQL的事务和隔离级别

    一.事务简介 事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成. 一个数据库事务通常包含了一个序列的对数据库的读/写操作.它的存在包含有以下两个目的: 为数据库操作序列提供 ...

随机推荐

  1. java 基础知识(三)

    Arraylist与Vector的区别 清浅池塘 程序员,专栏:Java那些事儿唯一作者,咨询前请先点详细资料   162 人赞同了该文章 这几天工作有点忙,有很多代码需要写,更新文章有点慢,说声抱歉 ...

  2. requests库获取响应流进行转发

    遇到了一个问题,使用requests进行转发 requests响应流的时候,出现各种问题,问题的描述没有记录,不过Debug以下终于解决了问题.......下面简单的描述解决方案 response = ...

  3. MySQL 中 InnoDB 支持的四种事务隔离级别名称,以及逐 级之间的区别?

    SQL 标准定义的四个隔离级别为: 1.read uncommited :读到未提交数据 2.read committed:脏读,不可重复读 3.repeatable read:可重读 4.seria ...

  4. 记一次 Nuxt 3 在 Windows 下的打包问题

    0. 背景 之前用 Nuxt 3 写了公司的官网,包括了样式.字体图标.图片.视频等,其中样式和字体图标放在了 assets/styles 和 assets/fonts 目录下,而图片和视频则放在了 ...

  5. 如何在Ubuntu 18.04 LTS上安装和配置MongoDB

    MongoDB是一款非关系型数据库,提供高性能,高可用性和自动扩展企业数据库. MongoDB是一个非关系型数据库,因此您不能使用SQL(结构化查询语言)插入和检索数据,也不会将数据存储在MySQL或 ...

  6. 第一天·浏览器内核及Web标准

    一·浏览器及浏览器内核 1.常见的浏览器 (1)IE浏览器 IE是微软公司旗下浏览器,是目国内用户量最多的浏览器.IE诞生于1994年,当时微软为了对抗市场份额占据将近百分之九十的网景Netscape ...

  7. 前端每日实战:134# 视频演示如何用 CSS 和 GSAP 创作一个树枝发芽的 loader

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/LJmpXZ 可交互视频 此视频是可 ...

  8. tomcat启动报错:A child container failed during start

    环境:maven3.3.9+jdk1.8+tomcat8.5 错误详细描述: 严重: A child container failed during start java.util.concurren ...

  9. new String比字符串池浪费空间,为什么要用它?

    对于下面程序中:ss0 = new String( "hello" );是用new()来新建对象的,存于堆中.每调用一次就会创建一个新的对象.当然从节省空间的角度来讲,肯定不如st ...

  10. Vue路由跳转时修改页面标题

    1 在main.js中添加如下代码 import Vue from 'vue' import App from './App.vue' import router from './router' // ...