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:
Just wondering if it best practice to use parallel streams with blocking I/O calls ?
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 returnsListenableFuture
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 anAsyncClientHttpRequestFactory
.
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With