首页
学习
活动
专区
圈层
工具
发布

接口响应慢?那是你没用 CompletableFuture 来优化!

很多人都有过这种体验:明明一个接口逻辑挺简单,但就是慢,特别是涉及数据库查询、远程调用的时候,串起来一跑,秒变蜗牛。比如查用户详情,要查用户基本信息,还得拉订单,再去查积分,你顺序写下来,接口响应可能就接近一秒了,用户体验直接崩掉。

串行的写法

一般我们最常见的写法大概是这样:

public UserDetailDTO getUserDetail(Long userId) {

  User user = userService.getUserById(userId);

  List<Order> orders = orderService.getOrders(userId);

  int points = pointsService.getPoints(userId);

  return new UserDetailDTO(user, orders, points);

}

逻辑直观没问题,就是三个调用全是串行的。如果每个都要 300ms,总耗时差不多就是 900ms。

用 CompletableFuture 并行请求

其实这些调用之间并没有依赖关系,完全可以同时发出去,这时候CompletableFuture就能派上用场。我们只需要一个线程池,就能把三个调用变成并行的:

public UserDetailDTO getUserDetail(Long userId) {

  ExecutorService executor = Executors.newFixedThreadPool(10);

  CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(

      () -> userService.getUserById(userId), executor);

  CompletableFuture<List<Order>> ordersFuture = CompletableFuture.supplyAsync(

      () -> orderService.getOrders(userId), executor);

  CompletableFuture<Integer> pointsFuture = CompletableFuture.supplyAsync(

      () -> pointsService.getPoints(userId), executor);

  CompletableFuture.allOf(userFuture, ordersFuture, pointsFuture).join();

  try {

      returnnew UserDetailDTO(userFuture.get(), ordersFuture.get(), pointsFuture.get());

  } catch (Exception e) {

      thrownew RuntimeException(e);

  }

}

这样三个请求并行发出,总耗时基本就是最慢的那个,大概 300ms 左右,性能直接翻倍。

异常处理

实际用的时候,不可能所有服务都百分百可用,有一个挂了怎么办?CompletableFuture给了我们优雅的处理方式,比如兜底返回:

userFuture = userFuture.exceptionally(ex -> {

  log.error("查询用户失败", ex);

  return new User(); // 返回一个默认对象,避免整个接口报错

});

这样就算某个子任务出错,也不会把整个接口拖死。

超时控制

光是并行还不够,有时候下游服务超时不返回,那接口还是会卡住。我们可以给CompletableFuture加上超时控制:

public static <T> T getWithTimeout(CompletableFuture<T> future, long timeout, TimeUnit unit) {

  try {

      return future.get(timeout, unit);

  } catch (TimeoutException e) {

      future.cancel(true); // 超时后取消任务

      throw new RuntimeException("调用超时", e);

  } catch (Exception e) {

      throw new RuntimeException("任务执行失败", e);

  }

}

用的时候就像这样:

User user = getWithTimeout(userFuture, 500, TimeUnit.MILLISECONDS);

List<Order> orders = getWithTimeout(ordersFuture, 500, TimeUnit.MILLISECONDS);

int points = getWithTimeout(pointsFuture, 500, TimeUnit.MILLISECONDS);

这样即使下游服务出问题,也不会把整个接口拖到 10 秒以上,前端能尽快得到响应。

接口慢,很多时候不是业务逻辑复杂,而是调用方式没优化。用CompletableFuture并行发请求,加上异常兜底和超时控制,接口性能基本能提升一大截。在微服务环境下尤其明显,一个请求要调好几个服务,如果还在串行等待,那真的是浪费时间。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OB6qRr9GlJTQolj9mzs_NYGQ0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券