Spring RestTemplate - async vs sync restTemplate

Я написал следующий код для проверки производительности как Synt 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 мс при вызове как асинхронных, так и синхронных вызовов.

Я делаю что-то неправильное, поскольку я ожидал бы, что вызов асинхронного вызова будет намного быстрее?

Ответ 1

Я бы сказал, что вы упускаете реальные преимущества AsyncRest здесь. Вы должны добавлять обратные вызовы к каждому отправляемому запросу, чтобы ответ обрабатывался только при его наличии.

Действительно, метод getForEntity в AsyncRestTemplate возвращает 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());
            }
        });
}

Ответ 2

Сложная вещь с 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, lambdas и ListenableFuture/CompletingFuture или композиционные библиотеки.

Ответ 3

Методы AsyncRestTemplate устарели, начиная с Spring5. Существуют ли другие альтернативные примеры с последним API Spring5 для аналогичной программы.