Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple asynchronous HTTP requests using Resttemplate

I have a service which uses springs RestTemplate to call out to multiple urls.

To improve performance I'd like to perform these requests in parallel. Two options available to me are:

  • java 8 parallel streams leveraging the fork-join common pool
  • completable future using isolated thread pool

Just wondering if it best practice to use parallel streams with blocking I/O calls ?

like image 450
Sam Avatar asked Jan 26 '23 18:01

Sam


2 Answers

A ForkJoinPool isn't ideal for doing IO work since you don't gain any of the benefits of its work stealing properties. If you planned to use the commonPool and other parts of your app did as well, you might interfere with them. A dedicated thread pool, an ExecutorService for example, is probably the better solution among those two.

I'd like to suggest something even better. Instead of writing all the async wrapping code yourself, consider using Spring's AsyncRestTemplate. It's included in the Spring Web library and its API is almost identical to RestTemplate.

Spring's central class for asynchronous client-side HTTP access. Exposes similar methods as RestTemplate, but returns ListenableFuture wrappers as opposed to concrete results.

[...]

Note: by default AsyncRestTemplate relies on standard JDK facilities to establish HTTP connections. You can switch to use a different HTTP library such as Apache HttpComponents, Netty, and OkHttp by using a constructor accepting an AsyncClientHttpRequestFactory.

ListenableFuture instances can easily be converted to CompletableFuture instances through ListenableFuture::completable().

As noted in the Javadoc, you can control what async mechanism you want to use by specifying a AsyncClientHttpRequestFactory. There are a number of built-in implementations, for each of the libraries listed. Internally, some of these libraries might do what you suggested and run blocking IO on dedicated thread pools. Others, like Netty (if memory serves), use non-blocking IO to run the connections. You might gain some benefit from that.

Then it's up to you how you reduce the results. With CompletableFuture, you have access to the anyOf and allOf helpers and any of the combination instance methods.

For example,

URI exampleURI = URI.create("https://www.stackoverflow.com");

AsyncRestTemplate template = new AsyncRestTemplate/* specific request factory*/();
var future1 = template.exchange(exampleURI, HttpMethod.GET, null, String.class).completable();
var future2 = template.exchange(exampleURI, HttpMethod.GET, null, String.class).completable();
var future3 = template.exchange(exampleURI, HttpMethod.GET, null, String.class).completable();

CompletableFuture.allOf(future1, future2, future3).thenRun(() -> {
    // you're done
});

AsyncRestTemplate has since been deprecated in favor of Spring Web Flux' WebClient. This API is considerably different so I won't go into it (except to say that it does let you get back a CompletableFuture as well).

like image 106
Sotirios Delimanolis Avatar answered Jan 31 '23 20:01

Sotirios Delimanolis


Completable future would be a better way to do this, as it is semantically more related to the task and you might keep the code flow going while the task proceeds.

If you use streams, beside the awkwardness of lambdas with exception handling inside and the fact that it is not so related to the task, semantically as in a pipeline, you will have to wait for all of them to finish, even if they are occuring in parallel. To avoid that you would need futures, but then you will be back to the first solution.

You might consider a mix, using streams to create the futures. But given that it is a blocking IO set of requests, you will probably not have enough requests or time to take advantage of the parallel streams, the library will probably not split the tasks in parallel for you and you will be better of with a loop.

like image 23
Victor Avatar answered Jan 31 '23 20:01

Victor