• 【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。


    前言

      工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回。

      还可能在微服务中遇到类似的情况,某个服务的接口,要使用好几次feign去调用其他服务的方法获取数据,最后拿到想要的值并封装返回给前端。

      这样的场景下,当某个或多个rpc调用的方法比较耗时,整个接口的响应就会非常慢。Java8之后,有一个工具非常适合处理这种场景,就是CompletableFuture。


    场景

      本章主要讲解CompletableFuture的并行处理用法,来针对这种很常见的场景,帮助大家快速掌握并应用到实际工作当中。CompletableFuture内部的用法还有许多,但个人用到的场景大多都是并行处理,对其他场景感兴趣的小伙伴可以另行百度搜索。

    场景说明:

    写一个接口,调用另外两个HTTP接口,分别获取二十四节气和星座,最后放在一起返回。


    用法

    1、在线API

    我们访问极速数据网站https://www.jisuapi.com ,注册一个账号,就可以免费使用里面的一些在线API,平均每天有100次免费机会,对于我这样经常本地做一些测试的人来说完全够用了。

    这里,我使用了其中的查询二十四节气API,和查询星座API,后面会提供案例代码,也可以直接使用我的。

    111.jpg


    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秒左右才返回。

    222.jpg


    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效果

    可以看到,时间缩短了一倍。

    333.jpg


    9、思考

    如果在微服务中,有一个很复杂的业务需要远程调用5个第三方laji厂家的接口,每个接口假设都耗时5秒,使用CompletableFuture并行处理最终需要多久?
    答案是肯定的,同步查询需要25秒左右,CompletableFuture并行处理还是5秒左右,也就是说,同一个接口中,调用的耗时接口越多,CompletableFuture优化的幅度就越大。


    示例代码

    可以下载我的完整示例代码本地按需测试,里面有我的极速数据API的key,省得自己注册账号了,每天免费就100次,先到先得哦。

    链接:https://pan.baidu.com/doc/share/P_Jn_x22fos0ED3YEnqI8A-232386145447394
    提取码:piil



    觉得有帮助的话,就请顺手点个【推荐】吧,本人定期分享工作中的经验及趣事,原创文章均为手打,喜欢的话也可以关注一下哦~

  • 相关阅读:
    [Micropython]发光二极管制作炫彩跑马灯
    如何在MicroPython TPYBoard 添加自定义类库
    micropython TPYBoard v202 超声波测距
    简易排水简车的制作 TurnipBit 系列教程
    TPYBoard v102 驱动28BYJ-48步进电机
    使用mksdcard管理虚拟SD卡
    使用 DX 编译 Android应用
    常用ADB的用法
    在命令行创建、删除和浏览AVD、使用android模拟器
    Android-SDK下目录结构
  • 原文地址:https://www.cnblogs.com/fulongyuanjushi/p/16198440.html
Copyright © 2020-2023  润新知