2015-07-22 23 views
5

Ho scritto il seguente codice per testare le prestazioni di sync RestTemplate e AsyncRestTemplate. L'ho eseguito poche volte manualmente su POSTMAN.Spring RestTemplate - async vs sync restTemplate

Siamo solo di passaggio 10 referenze in una chiamata GET in modo da poter tornare 10 collegamenti:

RestTemplate - sincrona e torna a 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 - asincrono e torna a 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()); 
} 

Nella maggior parte dei casi, entrambi i metodi restituiscono effettivamente i risultati con un tempo molto simile, con una media di 2800 ms in entrambe le chiamate asincrone e di sincronizzazione.

Sto facendo qualcosa di sbagliato in quanto mi sarei aspettato che la chiamata asincrona fosse molto più veloce?

risposta

4

La cosa complicata con Java Future è che non è componibile ed è davvero facile da bloccare.

In questo caso, chiamare future.get() rende il blocco di codice e attendere fino a quando la risposta è tornata. In realtà, questo approccio rende le chiamate sequenziali e non sfrutta la natura asincrona di questa implementazione RestTemplate.

Il modo più semplice per risolvere questo problema è quello di separare in due cicli:

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

Ovviamente ci sono più eleganti soluzioni, soprattutto se si utilizza sciama JDK8, lambda e ListenableFuture/CompletableFuture o librerie di composizione.

+0

Ciao, grazie per la tua risposta. Mi chiedo però quando chiamiamo ResponseEntity entity = future.get() nel tuo codice, non lo blocca anche il codice in modo che il ciclo for non proceda fino a quando la risposta non viene ricevuta? Riesco a vedere un miglioramento marginale nel tempo in cui la chiamata ritorna in 2500ms o giù di lì, ma non è sostanziale. – Simon

+0

sì, future.get() blocca ma a quel punto tutte le richieste sono già state inviate. Se puoi utilizzare JDK8 CompletableFutures o un'altra libreria di composizione, potresti avere qualcosa di più efficiente. Quando si misura questo, tenere presente che la creazione di un RestTemplate/AsyncRestTemplate richiede tempi e risorse e dovrebbe essere eseguita una sola volta (e non dovrebbe contare nel timer) –

3

Direi che ti stai perdendo i reali vantaggi dell'AsyncRest qui. È necessario aggiungere callback a ciascuna richiesta che si sta inviando in modo che la risposta sia elaborata solo quando disponibile.

In effetti, il metodo getForEntity di un AsyncRestTemplate restituisce un ListenableFuture a cui è possibile connettere un'attività di callback. Vedere il documento ufficiale ListenableFuture per ulteriori informazioni.

Per esempio nel tuo caso potrebbe essere:

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