我不是多线程专家,但我发现当前使用ExecutorService
的代码存在一些性能问题。
我正在开发一个项目,我需要对我的服务器进行HTTP URL调用,如果需要花费太长时间来响应,那么就会超时。目前它正在返回简单的JSON字符串..
我目前的要求是10 ms
。在10 ms
内,它应该能够从服务器获取数据。我猜它是可能的,因为它只是对同一数据中心内的服务器的HTTP调用。
我的客户端程序和实际服务器位于同一个数据中心内,并且它们之间的ping时间延迟为0.5 ms
,所以它应该可以确定..
我正在使用RestTemplate
进行网址调用。
以下是我为我撰写的使用ExecutorService
和Callables
的代码 -
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
中的代码,该类调用getData
类URLTest
中的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
我不确定是否应该使用这个或其他东西..
答案 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才被设计出来。