【问题标题】:How to improve the performance while using ExecutorService with thread timeout capabilities?如何在使用具有线程超时功能的 ExecutorService 时提高性能?
【发布时间】:2014-01-20 18:02:28
【问题描述】:

我不是多线程专家,但我发现当前使用 ExecutorService 的代码存在一些性能问题。

我正在处理一个项目,在该项目中我需要对我的服务器进行 HTTP URL 调用,如果响应时间过长,则调用超时。目前它正在返回简单的 JSON 字符串..

我目前的要求是10 ms。在10 ms 内,它应该能够从服务器取回数据。我想这是可能的,因为它只是对同一数据中心内服务器的 HTTP 调用。

我的客户端程序和实际服务器在同一个数据中心内,并且它们之间的 ping 时间延迟为0.5 ms,所以它应该是可行的..

我使用RestTemplate 来进行 URL 调用。

下面是我为我编写的代码,它使用ExecutorServiceCallables -

public class URLTest {

    private ExecutorService executor = Executors.newFixedThreadPool(10);

    public String getData() {
        Future<String> future = executor.submit(new Task());
        String response = null;

        try {
            System.out.println("Started..");
            response = future.get(100, TimeUnit.MILLISECONDS);
            System.out.println("Finished!");
        } catch (TimeoutException e) {
            System.out.println("Terminated!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        return response;
    }
}

下面是我的任务类,它实现了Callable 接口 -

class Task implements Callable<String> {

    private RestTemplate restTemplate = new RestTemplate();

    public String call() throws Exception {
        //  TimerTest timer = TimerTest.getInstance();  // line 3
            String response = restTemplate.getForObject(url, String.class);
        //  timer.getDuration();    // line 4

        return response;

    }
}

下面是我在另一个类DemoTest 中的代码,它调用URLTest500 times 中的getData 方法,并端到端测量它的第95 个百分位 -

public class DemoTest { 
   public static void main(String[] args) {

        URLTest bc = new URLTest();

        // little bit warmup
        for (int i = 0; i <= 500; i++) {
            bc.getData();
        }

        for (int i = 0; i <= 500; i++) {
            TimerTest timer = TimerTest.getInstance(); // line 1
            bc.getData();
            timer.getDuration(); // line 2
        }

        // this method prints out the 95th percentile
        logPercentileInfo();

    }
}   

使用上面的代码,我总是将第 95 个百分位视为14-15 ms(这对我的用例不利,因为它是端到端流,这就是我需要衡量的)。

我很惊讶为什么? ExectuorFramework 是否在这里添加了所有延迟?可能是每个任务都提交了,提交线程一直在等待(通过future.get)直到任务完成..

我的主要目标是尽可能减少这里的延迟。我的用例很简单,在启用 TIMEOUT 功能的情况下对我的一台服务器进行 URL 调用,这意味着如果服务器需要大量时间来响应,然后超时整个通话。客户将从那里调用我们的代码,该应用程序也可以是多线程的..

有什么我遗漏的东西或我需要使用的ExecutorService 的其他风格吗?我怎样才能在这里提高我的表现?任何建议都会有很大帮助..

任何示例都将不胜感激。我正在阅读有关ExecutorCompletionService 的信息,不确定是否应该使用这个或其他东西..

【问题讨论】:

  • 真的不明白您希望通过摆弄线程池实现来实现 Web 服务器性能吗?看看网络服务器和网络客户端......!您需要在 Web 客户端上设置超时,以便它真正放弃。随着未来的超时,你只是放弃了任务,但工作线程仍然坐在那里试图做它直到它完成。
  • 这对我来说看起来很单线程......
  • 这里不是执行器框架的问题。有些事情花费的时间比你预期的要长。我建议考虑运行 Java 分析器。您还可以记录系统各个部分的挂钟毫秒数,以查看花费了这么长时间。
  • 此外,即使您的作业超时,它也会继续在后台执行并占用系统资源。杀死 RPC 很重要。
  • @pamphlet:我的用例需要有同步和异步方法,一个用于阻止调用..另一个用于发送未来..根据这些方法,客户端可以调用任何方法他们喜欢..所以这就是我有这个的原因..

标签: java multithreading performance executorservice resttemplate


【解决方案1】:

至于您观察到您在外部测量 15 毫秒,但在内部仅测量 3 毫秒,我敢打赌 RestTemplate 的构造会有所不同。这可以通过重构来解决。

请注意,RestTemplate 是一个重量级的线程安全对象,旨在部署为应用程序范围的单例。您当前的代码严重违反了此意图。


如果你需要异步 HTTP 请求,你真的应该使用一个异步 HTTP 库,比如 AsyncHttpClient,它基于下面的 Netty,它再次基于 Java NIO。这意味着您不需要为每个未完成的 HTTP 请求占用一个线程。 AsyncHttpClient 也适用于 Futures,因此您将拥有一个您习惯的 API。它还可以与回调一起使用,这是异步方法的首选。

但是,即使您保留当前的同步库,您至少应该在 REST 客户端上配置一个超时,而不是让它运行。

【讨论】:

  • 谢谢你的建议。有什么想法我应该做些什么改变吗?如果您可以提供一个带有 AsyncHttpClient 的示例,那就太好了。我只是想了解它如何适合我当前的用例..
  • 要验证这个假设,只需将 RestTemplate 设为静态即可。
  • AsyncHttpClient 本身并不具备 RESTful 的便利性,但可能有一些库可以处理它。另一方面,我会先看看RestTemplate 本身在这方面的能力。
  • 不管是静态的还是实例的。? RestTemplate 用于发出 HTTP 请求的方法是线程安全的,因此无论您是否有每个 Task 实例的 RestTemplate 实例或所有 Task 实例的共享实例都是无关紧要的(垃圾收集除外)我猜..如果我错了,请纠正我..
  • RestTemplate 是一个重量级的对象,维护着完整的 IO 基础设施、连接池等。它肯定会产生巨大的影响。也就是说,除非我对RestTemplate的设计完全错误,这也是可能的。
【解决方案2】:

然后再次运行程序,它将开始给我第 95 个百分位数为 3 毫秒。所以不知道为什么端到端流给我 95% 为 14-15 毫秒

您生成任务的速度比处理它们的速度要快。这意味着您运行测试的时间越长,它在排队时就越落后。我希望如果您提出这 2000 个请求,您会看到高达现在的 4 倍的延迟。瓶颈可能在客户端(在这种情况下,更多线程会有所帮助),但很可能瓶颈在服务器端,在这种情况下,更多线程可能会使情况变得更糟。


HTTP 的默认行为是为每个请求建立一个新的 TCP 连接。即使您有两台机器并排,新 TCP 连接的连接时间也可以轻松达到 20 毫秒。我建议考虑使用 HTTp/1.1 并维护persistent connection.

顺便说一句,您可以在 0.5 毫秒内从伦敦的一侧 ping 到另一侧。但是,使用 HTTP 可靠地低于 1 毫秒是很棘手的,因为该协议不是为低延迟而设计的。它专为在高延迟网络上使用而设计。

注意:您无法看到低于 25 毫秒的延迟,而 100 毫秒对于大多数 Web 请求来说已经足够快了。 HTTP 就是基于这些假设而设计的。

【讨论】:

  • 感谢彼得的建议。我想告诉你一件非常奇怪的事情。也许这可以给你一些线索 - 如果我发表评论,我发现这里很奇怪的一件事是去掉 DemoTest 类中的第 1 行和第 2 行,取消注释 Task 类中的第 3 行和第 4 行,然后再次运行程序,它将开始给我 95% 的时间为 3 毫秒。所以不知道为什么端到端流程给了我 95% 的 14-15 毫秒?
  • 解释,OP 有 3 毫秒用于 REST 调用本身,但 15 毫秒用于未来调用。
  • 启动线程的延迟也会增加。您的代码在执行 10,000 次之前不会被编译,我通常会忽略基准测试的前 10K 或前 2 秒。
  • @PeterLawrey 回答这个问题。在测量之前我已经运行了几次。目前我正在运行 500 次以进行热身,然后我开始测量呼叫,如我的DemoTest class 所示。我可以增加预热次数,看看它是否会影响任何性能..
  • @Webby 在我看来,您生成请求的速度比处理请求的速度要快,因此它们正在排队。随着队列越来越长,运行时间越长应该看起来越慢。看我的回答。
猜你喜欢
  • 2016-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-31
  • 1970-01-01
相关资源
最近更新 更多