【Java分享客棧】一文搞定CompletableFuture並行處理,成倍縮短查詢時間。
- 2022 年 4 月 27 日
- 筆記
- CompletetableFuture, JAVA, springboot, 代碼優化, 異步編排, 程序員
前言
工作中你可能會遇到很多這樣的場景,一個接口,要從其他幾個service調用查詢方法,分別獲取到需要的值之後再封裝數據返回。
還可能在微服務中遇到類似的情況,某個服務的接口,要使用好幾次feign去調用其他服務的方法獲取數據,最後拿到想要的值並封裝返回給前端。
這樣的場景下,當某個或多個rpc調用的方法比較耗時,整個接口的響應就會非常慢。Java8之後,有一個工具非常適合處理這種場景,就是CompletableFuture。
場景
本章主要講解CompletableFuture的並行處理用法,來針對這種很常見的場景,幫助大家快速掌握並應用到實際工作當中。CompletableFuture內部的用法還有許多,但個人用到的場景大多都是並行處理,對其他場景感興趣的小夥伴可以另行百度搜索。
場景說明:
寫一個接口,調用另外兩個HTTP接口,分別獲取二十四節氣和星座,最後放在一起返回。
用法
1、在線API
我們訪問極速數據網站//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 = "//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 = "//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次,先到先得哦。
鏈接://pan.baidu.com/doc/share/P_Jn_x22fos0ED3YEnqI8A-232386145447394
提取碼:piil
覺得有幫助的話,就請順手點個【推薦】吧,本人定期分享工作中的經驗及趣事,原創文章均為手打,喜歡的話也可以關注一下哦~