如何在使用具有线程超时function的ExecutorService时提高性能?
我不是multithreading专家,但我发现当前使用ExecutorService
代码存在一些性能问题。
我正在开发一个项目,在这个项目中我需要对我的服务器进行HTTP URL调用,如果响应时间过长则会超时。 目前它正在返回简单的JSON字符串..
我当前的要求是10 ms
。 在10 ms
它应该能够从服务器获取数据。 我猜它是可能的,因为它只是对同一数据中心内的服务器的HTTP调用。
我的客户端程序和实际服务器在同一个数据中心内,并且它们之间的ping时间延迟为0.5 ms
,所以它应该是可行的。
我正在使用RestTemplate
进行URL调用。
下面是我为我编写的使用ExecutorService
和Callables
–
public class URLTest { private ExecutorService executor = Executors.newFixedThreadPool(10); public String getData() { Future 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 { 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
代码,它在URLTest
类中调用getData
方法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),直到任务完成为止。
我的主要目标是尽可能减少延迟。我的用例很简单,在启用了TIMEOUTfunction的情况下对我的某个服务器进行URL调用,这意味着如果服务器花了很多时间来响应,那么Timeout整个电话。 客户将从那里可以multithreading的应用程序调用我们的代码。
有什么我缺少的或我需要使用的其他一些ExecutorService
吗? 我怎样才能提高我的表现? 任何建议都会有很大的帮助..
任何例子都将非常感激..我正在阅读有关ExecutorCompletionService
不确定我是否应该使用这个或其他东西..
至于你的观察,你在外面测量15毫秒,但内部只测量3毫秒,我的赌注是RestTemplate
的构造有所不同。 这可以通过重构来解决。
请注意, RestTemplate
是一个重量级,线程安全的对象,旨在部署为应用程序范围的单例 。 您当前的代码严重违反了此意图。
如果您需要异步HTTP请求,您应该使用异步HTTP库,例如基于下面的Netty的AsyncHttpClient
,它再次基于Java NIO。 这意味着您不需要为每个未完成的HTTP请求占用一个线程。 AsyncHttpClient
也适用于Futures,因此您将拥有一个习惯的API。 它也可以与回调一起使用,这是异步方法的首选。
但是,即使您保留当前的同步库,也应该至少在REST客户端上配置超时,而不是让它运行。
然后再次运行该程序它将开始给我95%百分位为3毫秒。 所以不确定为什么端到端流量给我95%的百分位数为14-15毫秒
您正在以比处理它们更快的速度生成任务。 这意味着你运行测试的时间越长,它排队的时间就越远。 我希望如果你提出这2000个请求,你会看到延迟达到你现在所做的4倍。 瓶颈可能在客户端(在这种情况下,更multithreading会有所帮助),但很可能瓶颈在服务器端,在这种情况下,更多的线程可能会使它变得更糟。
HTTP的默认行为是为每个请求建立新的TCP连接。 即使您有两台并排的计算机,新TCP连接的连接时间也可以轻松达到20毫秒。 我建议使用HTTp / 1.1并保持持久连接。
顺便说一下你可以在0.5毫秒内从伦敦的一边ping到另一边。 但是,使用HTTP可靠地低于1毫秒是棘手的,因为协议不是为低延迟而设计的。 它专为在高延迟网络上使用而设计。
注意:您无法看到低于25毫秒的延迟,100毫秒足够快,足以满足大多数Web请求。 正是出于这种假设,HTTP才被设计出来。
- 完成所有ExecutorService任务后,程序不会立即终止
- Java – 将工作分割为多个线程
- 删除ThreadPoolExecutor的所有排队任务
- 执行程序优于新线程的优点
- Java ExecutorService暂停/恢复特定线程
- java Fork / Join池,ExecutorService和CountDownLatch
- Java:带有Callables的ExecutorService:invokeAll()和future.get() – 结果是否正确?
- 如何决定是使用newCachedThreadPool还是newFixedThreadPool?
- ExecutorService的未来任务并未真正取消