web-dev-qa-db-ja.com

Spring RestTemplate-非同期と同期RESTTemplate

同期RestTemplateとAsyncRestTemplateの両方のパフォーマンスをテストするために、次のコードを作成しました。 POSTMANで手動で数回実行しました。

10個のリンクを返すことができるように、10個の参照をGET呼び出しに渡しているだけです。

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

ほとんどの場合、両方のメソッドは実際に非常に近い時間で結果を返し、非同期呼び出しと同期呼び出しの両方で平均2800msを返します。

非同期呼び出しがはるかに高速になると予想していたので、間違ったことをしていますか?

13
Simon

ここでAsyncRestの本当の利点を逃していると思います。送信する各リクエストにコールバックを追加して、応答が利用可能な場合にのみ処理されるようにする必要があります。

実際、getForEntityAsyncRestTemplateメソッドは、コールバックタスクを接続できる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());
            }
        });
}
15
Ugo Giordano

Java Futureの扱いにくいことは、構成可能ではなく、ブロックするのは本当に簡単だということです。

この場合、future.get()を呼び出すと、コードブロックが作成され、応答が返されるまで待機します。実際、このアプローチは順次呼び出しを行い、このRestTemplate実装の非同期の性質を活用しません。

これを修正する最も簡単な方法は、2つのループに分けることです。

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ストリーム、ラムダ、ListenableFuture/CompletableFutureまたはコンポジションライブラリを使用する場合は特に、よりエレガントなソリューションがあります。

8
Brian Clozel

現在、AsyncRestTemplate@Deprecatedに賛成WebClient。したがって、このクラスを使用する必要はありません!

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

3
membersound