Spring RestTemplate - 异步 vs 同步 restTemplate

20

我编写了以下代码来测试同步RestTemplate和AsyncRestTemplate的性能。我只是在POSTMAN上手动运行了几次。

我们只是传递了10个引用到GET调用中,以便我们可以返回10个链接:

RestTemplate - 同步并在2806ms内返回:

ArrayList<String> references = new ArrayList<>();
ArrayList<String> links = new ArrayList<>();
RestTemplate restTemplate = new RestTemplate(); 
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
for (int i = 0; i < 10; i++) {
    ResponseEntity<String> resource = restTemplate.getForEntity(references.get(i), String.class);
    links.add(resource.getBody().toString());
}

RestTemplate - 异步并在2794ms内返回:

//Creating a synchronizedList so that when the async resttemplate returns, there will be no concurrency issues
List<String> links = Collections.synchronizedList(new ArrayList<String>());

//CustomClientHttpRequestFactory just extends SimpleClientHttpRequestFactory but disables automatic redirects in SimpleClientHttpRequestFactory
CustomClientHttpRequestFactory customClientHttpRequestFactory = new CustomClientHttpRequestFactory();
//Setting the ThreadPoolTaskExecutor for the Async calls
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor pool = new org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor();
pool.setCorePoolSize(5);
pool.setMaxPoolSize(10);
pool.setWaitForTasksToCompleteOnShutdown(true);
pool.initialize();
//Setting the TaskExecutor to the ThreadPoolTaskExecutor
customClientHttpRequestFactory.setTaskExecutor(pool);

ArrayList<String> references = new ArrayList<>();
ArrayList<String> links = new ArrayList<>();
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(customClientHttpRequestFactory); 
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
for (int i = 0; i < 10; i++) {
    Future<ResponseEntity<String>> resource = asyncRestTemplate.getForEntity(references.get(i), String.class);
    ResponseEntity<String> entity = resource.get(); //this should start up 10 threads to get the links asynchronously
    links.add(entity.getBody().toString());
}

在大多数情况下,异步和同步调用的返回结果时间非常相似,平均为2800毫秒。

我做错了什么吗?我本来以为异步调用会快得多?


认为“async”就意味着更快是非常天真的想法。只有在正确的库实现和高负载(多个客户端)的情况下才有机会。在其他情况下,它只是关于API。 - Yura
3个回答

21

18
我觉得你可能没有意识到使用AsyncRest的真正好处。你应该为每个请求添加回调函数,这样只有在响应可用时才会处理响应。
实际上,在AsyncRestTemplate中,getForEntity方法返回一个ListenableFuture,你可以将回调任务连接到它上面。请参阅官方文档ListenableFuture以获取更多信息。
例如,在你的情况下,可以这样做:
for (int i = 0; i < 10; i++) {
     ListenableFuture<ResponseEntity<String>> response = asyncRestTemplate.getForEntity(references.get(i), String.class);
     response.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
            @Override
            public void onSuccess(ResponseEntity<String> result) {
                // Do stuff onSuccess 
                links.add(result.getBody().toString());
            }

            @Override
            public void onFailure(Throwable ex) {
                log.warn("Error detected while submitting a REST request. Exception was {}", ex.getMessage());
            }
        });
}

9
Java中Future的棘手之处在于它不可组合且很容易阻塞。
在这种情况下,调用future.get()会使您的代码阻塞并等待响应返回。实际上,这种方法进行了顺序调用,并没有利用这个RestTemplate实现的异步性质。
最简单的解决方法是将其分为两个循环。
ArrayList<Future<ResponseEntity<String>>> futures = new ArrayList<>();

for (String url : references.get()) {
    futures.add(asyncRestTemplate.getForEntity(url, String.class)); //start up to 10 requests in parallel, depending on your pool
}

for (Future<ResponseEntity<String>> future : futures) {
    ResponseEntity<String> entity = future.get(); // blocking on the first request
    links.add(entity.getBody().toString());
}

显然,有更加优雅的解决方案,特别是在使用JDK8流、lambda和ListenableFuture / CompletableFuture或组合库时。


嗨,感谢您的回复。不过我想知道,在您的代码中当我们调用ResponseEntity<String> entity = future.get()时,这是否也会阻塞代码,以至于for循环在收到响应之前无法继续执行?我可以看到调用返回大约2500ms左右的时间上有一些微小的改进,但并不实质性。 - Simon
2
是的,future.get() 会阻塞,但此时所有请求都已发送。如果您可以使用JDK8 CompletableFutures或其他组合库,则可以获得更高效的结果。在测量时,请记住创建RestTemplate/AsyncRestTemplate需要时间和资源,并且应该只执行一次(不应计入计时器)。 - Brian Clozel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接