I'm beginning a foray into the world of reactive Java programming using Webflux in Spring Boot.
I'm running into a scenario where it's really hard to do a certain database call reactively.
If I do a single blocking database call within a Mono, what happens?
The code would look something like this...
public Mono<ReturnThing> isThisAsyncOrNot() {
//Async, non-blocking API call
return webClient.doSomeAPIWork()
.flatMap(whatevers -> {
//Synchronous, blocking database call
ReturnThing returnThing= returnThingRepo.getByWhateverId(whatever.ID);
}
return returnThing;
});
}
Now yes, I know that there's an easy way to do this reactively. That's not the question I'm asking (in fact, the real code is quite a bit more complex).
What I really want to know is what that synchronous database call will do to my performance. Will the overall method still be async non-blocking (except during the part where the db call is made, which is blocking)? Or will this somehow ruin the whole reactive paradigm and cause the entire thing, start to finish, to be blocking?
Webflux InternalsReactor Netty is an asynchronous, event-driven network application framework built out of Netty server which provides non-blocking and backpressure-ready network engines for HTTP, TCP, and UDP clients and servers.
One such reactive asynchronous programming model for servers is the event loop model: Above, is an abstract design of an event loop that presents the ideas of reactive asynchronous programming: The event loop runs continuously in a single thread, although we can have as many event loops as the number of available cores.
What is Spring WebFlux ? Spring WebFlux is parallel version of Spring MVC and supports fully non-blocking reactive streams. It support the back pressure concept and uses Netty as inbuilt server to run reactive applications. If you are familiar with Spring MVC programming style, you can easily work on webflux also.
Backpressure in software systems is the capability to overload the traffic communication. In other words, emitters of information overwhelm consumers with data they are not able to process. Eventually, people also apply this term as the mechanism to control and handle it.
The golden rule to remember is that you can never make a blocking method non-blocking. You can go the other way around trivially, and you can do various things to not wreck the reactive paradigm entirely, but there's no way to turn it inherently asynchronous.
Or will this somehow ruin the whole reactive paradigm and cause the entire thing, start to finish, to be blocking?
Worse than this, unfortunately. Assuming it doesn't crash the application, it will cause one of the few reactive threads (or maybe the single reactive thread) to block while your blocking database call executes. This means that all other reactive operations that need to use that thread (this could quite feasibly be your entire application) will have to wait until that blocking database call finishes before they can be scheduled, which is a critical hit on performance.
The accepted way to deal with these sorts of situations (where you have a blocking call you need to make from a reactive chain) is to use the bounded elastic scheduler
, which delegates execution to a backend thread pool so as not to tie up the main event thread(s).
You need to understand the crux of reactive and you'll be able to answer this question on your own. Each operator in reactive processes items one-at-a-time. Each operator also has a queue where items can be queued if the operator is busy. Now consider the following code:
Flux.fromIterable(Arrays.asList(1,2,3,4,5))
.flatMap(a -> {
Thread.sleep(2000);
return Mono.just(a);
}).subscribe(System.out::println);
You'll see items are prints at intervals of 2 seconds. This is because flatMap
's thread is blocked for 2 seconds, and the items rest of the elements are queued up. flatMap
's thread has to become free if it wants to consume items from its queue.
In your case, you said that the DB call was blocking. What this would result in is the calling thread
getting blocked. So your method will neither be non-blocking
nor asynchronous
. However, do something like
Mono.just(whatever)
.subscribeOn(Schedulers.elastic())
.flatMap(whatever -> returnThingRepo.getByWhateverId(whatever.ID));
inside the flatMap
and your method would become non-blocking
and asynchronous
. This happens because the calling thread becomes free as soon as the .subscribeOn()
operator is called.
Hopefully this answered your question.
This does not break the reactive paradigm. Reactive, as I see it, is much more than non-blocking, asynchronous operations. The idiomaticity and easy multi-threading capabilities are a big selling point for me, besides NIO
.
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