【问题标题】:Multiple REST calls timing out in Spring Boot web applicationSpring Boot Web 应用程序中的多个 REST 调用超时
【发布时间】:2017-04-30 16:36:43
【问题描述】:

我创建了一个 Spring Boot (1.4.2) REST 应用程序。 @RestController 方法之一需要调用第 3 方 API REST 操作 (RestOp1),该操作返回 100-250 条记录。对于 RestOp1 返回的每条记录,在同一方法中,必须调用同一 3rd 方 API (RestOp2) 的另一个 REST 操作。我的第一次尝试涉及使用基于大小为 100 的固定线程池的 Controller 类级别 ExecutorService,以及返回与 RestOp2 响应对应的记录的 Callable:

// Executor thread pool - declared and initialized at class level
ExecutorService executor = Executors.newFixedThreadPool(100);

// Get records from RestOp1
ResponseEntity<RestOp1ResObj[]> restOp1ResObjList
  = this.restTemplate.exchange(url1, HttpMethod.GET, httpEntity, RestOp1ResObj[].class);
RestOp1ResObj[] records = restOp1ResObjList.getBody();

// Instantiate a list of futures (to call RestOp2 for each record)
List<Future<RestOp2ResObj>> futureList = new ArrayList<>();

// Iterate through the array of records and call RestOp2 in a concurrent manner, using Callables.
for (int count=0; count<records.length; count++) {

  Future<RestOp2ResObj> future = this.executorService.submit(new Callable<RestOp2ResObj>() {

    @Override
    public RestOp2ResObj call() throws Exception {
      return this.restTemplate.exchange(url2, HttpMethod.GET, httpEntity, RestOp2Obj.class);
    }
  };

  futureList.add(future);
});

// Iterate list of futures and fetch response from RestOp2 for each
// record. Build a final response and send back to the client.
for (int count=0; count<futureList.size(); count++) {

  RestOp2ResObj response = futureList.get(count).get();
  // use above response to build a final response for all the records.
}

上述代码的性能至少可以说是糟糕透了。 RestOp1 调用(仅调用一次)的响应时间约为 2.5 秒,而 RestOp2 调用(为每条记录调用)的响应时间约为 1.5 秒。但代码执行时间在 20-30 秒之间,而不是预期的 5-6 秒!我在这里遗漏了一些基本的东西吗?

【问题讨论】:

  • 你能添加你的executorService的初始化吗?
  • @ilopezluna 我在代码 sn-p 的顶部添加了执行器服务的初始化。那是该类的成员,显然是在相关方法之外声明的。
  • 好吧,我觉得还可以。您是否检查过您的任务是否已并行执行?例如记录线程名称,您可以在创建线程池时定义名称:nurkiewicz.com/2014/11/executorservice-10-tips-and-tricks.html
  • @ilopezluna 这是一组很棒的技巧。即使任务没有并行执行,我想知道什么是阻塞。我将尝试一些日志记录,希望能有所启发。有没有更好的方法/模式来做到这一点?
  • 我不知道有什么模式可以实现,我建议你记录线程的名称(看看它们是否并行运行),每个任务的执行时间并计算平均值时间(只是为了确保 api 的响应时间是您所期望的),然后您将获得所有信息来查看谁一直在花费时间。也许只是一个查询,它花费的时间比您预期的要多。试试看并告诉我们。

标签: multithreading spring-boot resttemplate


【解决方案1】:

您调用的服务是否足够快以每秒处理那么多请求?

有一个异步版本的 RestService 可用,称为 AsyncRestService。你为什么不使用它?

我可能会这样:

    AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(new ConcurrentTaskExecutor(Executors.newFixedThreadPool(100)));

    asyncRestTemplate.exchange("http://www.example.com/myurl", HttpMethod.GET, new HttpEntity<>("message"), String.class)
            .addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
                @Override
                public void onSuccess(ResponseEntity<String> result) {
                    //TODO: Add real response handling

                    System.out.println(result);
                }

                @Override
                public void onFailure(Throwable ex) {
                    //TODO: Add real logging solution

                    ex.printStackTrace();
                }
            });

【讨论】:

  • 是的,第三方服务可以处理更多的并发请求。 RestOp1 的单个响应时间为 1.5-2.5 秒,RestOp2 的响应时间为 1-1.5 秒。这就是为什么我预计通话的最大响应时间为 5-7 秒。我查看了 AsyncRestService,但我不知道如何将其配置为使用 Apache 的 AsyncHttpClient 之类的东西并处理成功、取消和错误条件。有什么好的代码示例(在 Spring Boot Web 应用程序上下文中)可以参考吗?
  • 我为你扩展了一点答案。
  • 感谢您的代码。由于我可能会调用asyncRestTemplate.exchange(...) 150-250 次,如何协调所有 ListenableFutureCallbacks 的完成? ListenableFuture> 是否会成为此服务方法的返回类型,将其发送回控制器?我试图弄清楚这部分,需要协调多个响应并将其发回。我对 ListenableFuture 和 DeferredResult 的理解很薄弱,这就是我遇到这么多困难的原因。
【解决方案2】:

您的问题涉及两个部分:

  1. 多个 API 异步回调
  2. 处理超时(回退)

这两个部分是相关的,因为您必须处理每次调用的超时。

您可以考虑使用 Spring Cloud(基于 Spring Boot)并使用一些基于 OSS Netflix 堆栈的开箱即用解决方案。

第一个(超时)应该是基于 feign 客户端的断路器 hystrix

第二个(多个请求)这是一个架构问题,使用本机 Executors 不是一个好主意,因为它不会扩展并且具有巨大的维护成本。您可以转发Spring Asynchrounous Methods,您将获得更好的结果并完全符合弹簧要求。

希望这会有所帮助。

【讨论】:

  • 我根据之前的答案开始研究 AsyncRestTemplate。在您提供的链接中,它似乎涵盖了在不同的线程中运行代码(由@Async 授权),这不是由 Spring 管理的?我有点不知道该走哪条路。
  • 一切都是关于坚持简单和不要重复你的自我原则。使用 @Async 将为您处理管理线程生命周期的麻烦。这是一个更解释的例子。 baeldung.com/spring-async
  • 我查看了示例链接。如果我只打一个电话,代码会使@Async 的好处显而易见。就我而言,我仍然需要担心如何协调多个 API 调用的结果并将它们作为单个响应发送回来。在那种情况下,我想知道@Async 将如何为我完成所有这些工作,如果我不提供一些线程同步代码的话。
  • 当然,您可以循环使用 @Async 注释的服务方法并将结果 Future 存储到列表中,然后在 while(true) 循环中您可以在 Future(s) 中探测结果建立你的结果。主要的好处是你根本不用和 Executor 打交道
猜你喜欢
  • 2016-05-22
  • 1970-01-01
  • 2022-01-07
  • 2017-08-09
  • 2015-11-25
  • 2015-01-26
  • 1970-01-01
  • 2020-07-17
  • 2018-01-30
相关资源
最近更新 更多