如何在使用具有线程超时功能的ExecutorService时提高性能?

时间:2014-01-20 18:02:29

标签: java multithreading performance executorservice resttemplate

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

我正在开发一个项目,我需要对我的服务器进行HTTP URL调用,如果需要花费太长时间来响应,那么就会超时。目前它正在返回简单的JSON字符串..

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

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

我正在使用RestTemplate进行网址调用。

以下是我为我撰写的使用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;
    }
}

下面是我的Task类,它实现了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中的代码,该类调用getDataURLTest中的500 times方法,并从头到尾衡量它的第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我不确定是否应该使用这个或其他东西..

2 个答案:

答案 0 :(得分:5)

至于你的观察结果,你在外面测量的是15毫秒,但在内部只测量了3毫秒,我的赌注是RestTemplate的构造有所不同。这可以通过重构来解决。

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


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

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

答案 1 :(得分:3)

  

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

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


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

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

注意:您无法看到低于25毫秒的延迟,100毫秒足够快,足以满足大多数网络请求。正是出于这种假设,HTTP才被设计出来。