SpringBoot项目中异步调用接口方式知多少?

2023年 8月 23日 82.5k 0

环境:springboot2.5.12

经常会遇到在项目中调用第三方接口的情景,你是如何调用的呢?同步?异步?

场景:

假设下单业务流程如下步骤:

1、查询用户信息。

2、查询库存信息。

3、查询活动信息(折扣)。

1、同步顺序调用

public boolean createOrder() {
  long start = System.currentTimeMillis() ;
  String userResult = restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
  String storageResult = restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
  String discountResult = restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ;
  // 这里合并请求结果处理
  System.out.println(Arrays.toString(new String[] {userResult, storageResult, discountResult})) ;
  System.out.println("传统方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
  return true ;
}
@GetMapping("/create")
public Object create() {
  return os.createOrder() ;
}

调用结果:

图片图片

接口一个一个调用,非常耗时。

2、多线程(Callable+Future)

public boolean createOrder2() {
  long start = System.currentTimeMillis() ;
  Callable userCallable = () -> {
    return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
  } ;
  Callable storageCallable = () -> {
    return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
  } ;
  Callable discountCallable = () -> {
    return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ;
  } ;
  FutureTask userTask = new FutureTask(userCallable) ;
  FutureTask storageTask = new FutureTask(storageCallable) ;
  FutureTask discountTask = new FutureTask(discountCallable) ;
  new Thread(userTask).start() ;
  new Thread(storageTask).start() ;
  new Thread(discountTask).start() ;
  try {
    String userResult = userTask.get() ;
    String storageResult = storageTask.get() ;
    String discountResult = discountTask.get() ;
    // 这里合并请求结果处理
    System.out.println(Arrays.toString(new String[] {userResult, storageResult, discountResult})) ;
  } catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
  }
  System.out.println("多线程方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
  return true ;
}

调用结果:

图片图片

这次耗时少了,性能明显提升了。但在项目中我们一般是禁止直接创建线程的,如果这是个高并发的接口,那么我们的程序很可能出现OOM的错误。

3、线程池(Callable+Future)防止内存溢出风险

ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue(1000)) ;
  public boolean createOrder3() {
  long start = System.currentTimeMillis() ;
  List results = new ArrayList(3) ;
  results.add(pool.submit(() -> {
      return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
  })) ;
  results.add(pool.submit(() -> {
      return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
  })) ;
  results.add(pool.submit(() -> {
    return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ;
  })) ;
  for (int i = 0, size = results.size(); i < size; i++) {
    try {
      System.out.println(results.get(i).get()) ;
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
  }
  System.out.println("线程池方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
  return true ;
}

调用结果:

图片图片

耗时和上一个基本一致,通过Future的方式有一个问题就是只能一个一个的取值,只有当前的返回数据了后才会继续往下执行。如果有其它的任务执行完,那没有轮到它也必须等待。

4、CompletionService(异步任务与使用已完成任务的结果分离),submit提交任务,take获取已经完成的任务,不用按照submit的顺序获取结果。

public boolean createOrder4() {
  long start = System.currentTimeMillis() ;
  CompletionService cs = new ExecutorCompletionService(pool) ;
  cs.submit(() -> {
    return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
  }) ;
  cs.submit(() -> {
    return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
  }) ;
  cs.submit(() -> {
    return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1}) ;
  }) ;
  for (int i = 2 ; i >=0; i--) {
    try {
      System.out.println(cs.take().get()) ;
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
  }
  System.out.println("CompletionService方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
  return true ;
}

调用结果:

图片图片

通过CompletionService方式不管任务添加的顺序是什么,只要通过take方法就能获取执行完的结果,如果没有任务执行完,take方法会阻塞。

5、CompletableFuture(异步任务编排),JDK1.8

public boolean createOrder5() {
  long start = System.currentTimeMillis() ;
  CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> {
    return restTemplate.getForObject("http://localhost:8080/users/{1}", String.class, new Object[] {1}) ;
  }) ;
    
  CompletableFuture storageFuture = CompletableFuture.supplyAsync(() -> {
    return restTemplate.getForObject("http://localhost:8080/storage/{1}", String.class, new Object[] {1}) ;
  }) ;
    
  CompletableFuture discountFuture = CompletableFuture.supplyAsync(() -> {
    return restTemplate.getForObject("http://localhost:8080/discount/{1}", String.class, new Object[] {1});
  }) ;
  CompletableFuture result = CompletableFuture
      .allOf(userFuture, storageFuture, discountFuture)
      .thenApply((Void) -> {
        List datas = new ArrayList() ;
        try {
          datas.add(userFuture.get()) ;
          datas.add(storageFuture.get()) ;
          datas.add(discountFuture.get()) ;
        } catch (InterruptedException | ExecutionException e) {
          e.printStackTrace();
        }
        return datas ;
      }).exceptionally(e -> {
        e.printStackTrace() ;
        return null ;
      }) ;
  try {
    System.out.println(result.get()) ;
  } catch (InterruptedException | ExecutionException e1) {
    e1.printStackTrace();
  }
  System.out.println("CompletableFuture方式耗时:" + (System.currentTimeMillis() - start) + "毫秒") ;
  return true ;
}

调用结果:

图片图片

CompletableFuture提供了非常强大的异步编程方法,可同步,可异步,可编排任务执行,异步通过回调的方式执行。该对象很多的一些方法与前端JavaScript中的Promise对象有点相像。

完毕!!!

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论