web-dev-qa-db-fra.com

Spring RestTemplate - async vs sync restTemplate

J'ai écrit le code suivant pour tester les performances de RestTemplate et AsyncRestTemplate de synchronisation. Je viens de l'exécuter quelques fois manuellement sur POSTMAN.

Nous passons juste 10 références dans un appel GET afin de pouvoir renvoyer 10 liens:

RestTemplate - synchrone et retourne en 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 - asynchrone et retourne en 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());
}

Dans la plupart des cas, les deux méthodes renvoient les résultats avec un temps très similaire, en moyenne 2800 ms dans les appels asynchrones et synchronisés.

Suis-je en train de faire quelque chose de incorrect car je m'attendais à ce que l'appel asynchrone soit beaucoup plus rapide?

13
Simon

Je dirais que vous manquez les vrais avantages de AsyncRest ici. Vous devez ajouter des rappels à chaque demande que vous envoyez afin que la réponse ne soit traitée que lorsqu'elle est disponible.

En effet, la méthode getForEntity d'un AsyncRestTemplate renvoie un ListenableFuture auquel vous pouvez connecter une tâche de rappel. Voir le document officiel ListenableFuture pour plus d'informations.

Par exemple, dans votre cas, cela pourrait être:

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());
            }
        });
}
15
Ugo Giordano

La chose délicate avec Java Future est que ce n'est pas composable et c'est vraiment facile à bloquer.

Dans ce cas, appeler future.get() crée votre bloc de code et attendez que la réponse soit de retour. En fait, cette approche effectue des appels séquentiels et ne tire pas parti de la nature asynchrone de cette implémentation de RestTemplate.

Le moyen le plus simple de résoudre ce problème est de le séparer en deux boucles:

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());
}

Évidemment, il existe des solutions plus élégantes, en particulier si vous utilisez des flux JDK8, des lambdas et des bibliothèques ListenableFuture/CompletableFuture ou des compositions.

8
Brian Clozel

De nos jours, AsyncRestTemplate est @Deprecated en faveur de WebClient. Donc, personne ne devrait plus utiliser cette classe!

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html

3
membersound