In this presentation Rossen Stoyanchev
from the Spring
team explains some of these points.
WebClient
will use a limited number of threads - 2 per core for a total of 12 threads
on my local machine - to handle all requests and their responses in the application. So if your application receives 100 requests
and makes one request to an external server for each, WebClient
will handle all of those using those threads in a non-blocking
/ asynchronous
manner.
Of course, as you mention, once you call block
your original thread will block, so it would be 100 threads + 12 threads for a total of 112 threads
to handle those requests. But keep in mind that these 12 threads do not grow in size as you make more requests, and that they don't do I/O heavy lifting, so it's not like WebClient
is spawning threads to actually perform the requests or keeping them busy on a thread-per-request fashion.
I'm not sure if when the thread is under block
it behaves the same as when making a blocking call through RestTemplate
- it seems to me that in the former the thread should be inactive
waiting for the NIO
call to complete, while in the later the thread should be handling I/O
work, so maybe there's a difference there.
It gets interesting if you begin using the reactor
goodies, for example handling requests that depend on one another, or many requests in parallel. Then WebClient
definitely gets an edge as it'll perform all concurrent actions using the same 12 threads, instead of using a thread per request.
As an example, consider this application:
@SpringBootApplication
public class SO72300024 {
private static final Logger logger = LoggerFactory.getLogger(SO72300024.class);
public static void main(String[] args) {
SpringApplication.run(SO72300024.class, args);
}
@RestController
@RequestMapping("/blocking")
static class BlockingController {
@GetMapping("/{id}")
String blockingEndpoint(@PathVariable String id) throws Exception {
logger.info("Got request for {}", id);
Thread.sleep(1000);
return "This is the response for " + id;
}
@GetMapping("/{id}/nested")
String nestedBlockingEndpoint(@PathVariable String id) throws Exception {
logger.info("Got nested request for {}", id);
Thread.sleep(1000);
return "This is the nested response for " + id;
}
}
@Bean
ApplicationRunner run() {
return args -> {
Flux.just(callApi(), callApi(), callApi())
.flatMap(responseMono -> responseMono)
.collectList()
.block()
.stream()
.flatMap(Collection::stream)
.forEach(logger::info);
logger.info("Finished");
};
}
private Mono<List<String>> callApi() {
WebClient webClient = WebClient.create("http://localhost:8080");
logger.info("Starting");
return Flux.range(1, 10).flatMap(i ->
webClient
.get().uri("/blocking/{id}", i)
.retrieve()
.bodyToMono(String.class)
.doOnNext(resp -> logger.info("Received response {} - {}", I, resp))
.flatMap(resp -> webClient.get().uri("/blocking/{id}/nested", i)
.retrieve()
.bodyToMono(String.class)
.doOnNext(nestedResp -> logger.info("Received nested response {} - {}", I, nestedResp))))
.collectList();
}
}
If you run this app, you can see that all 30 requests are handled immediately in parallel by the same 12 (in my computer) threads. Neat!
If you think you can benefit from such kind of parallelism in your logic, it's probably worth it giving WebClient
a shot.
If not, while I wouldn't actually worry about the "extra resource spending" given the reasons above, I don't think it would be worth it adding the whole reactor/webflux
dependency for this - besides the extra baggage, in day to day operations it should be a lot simpler to reason about and debug RestTemplate
and the thread-per-request
model.
Of course, as others have mentioned, you ought to run load tests to have proper metrics.
WebClient
, so the consideration is whether to move towardsRestTemplate
or perhaps the jdk'sHttpClient
to reduce thread consumption and abandon the need for webflux. Reading your answer, I believe that the cost of changing that tool outweighs the increased performance. The addition of gaining an edge when performing concurrent actions is a good one, and I'll keep that in mind in further development. – Mcknight