I have always been used to blocking programming and working in the Spring MVC framework. Recently, I considered learning reactive programming. I was full of doubts about how to convert the previous logic into a new style.
See the following processing(Pseudocode):
public Mono<List<String>> a() {
// 1...
List<String> strings = new ArrayList<>();
for (int i = 0; i < 100; i ) {
strings.add("hello " i);
}
Mono<List<String>> mono = Mono.just(strings);
// 2...
mono.subscribe(e -> {
b();
});
// 3...
mono.subscribe(e -> {
c();
});
mono.subscribeOn(Schedulers.boundedElastic());
return mono;
}
// Simulate a time-consuming process.
public void b() {
try {
Thread.sleep(100);
} catch (InterruptedException err) {
throw new RuntimeException(err);
}
}
// Simulate the process of requesting an external HTTP interface once.
public int c() {
try {
Thread.sleep(300);
} catch (InterruptedException err) {
throw new RuntimeException(err);
}
return 1;
}
I tried to convert it into code that conforms to the responsive programming style, but found that the time-consuming code logic has blocked the current thread, which is inconsistent with my expectation.
I tested Webflux and Tomcat respectively, and the results show that the performance of the former is very poor. I suspect that the IO thread is blocked, which can be seen from the thread sleep time.
CodePudding user response:
Thread.sleep()
will pause the JVM thread running the request. You can't call this method with a Spring WebFlux application. By design WebFlux uses very few threads to handle the requests to avoid context switching but if your code intentionally blocks them you break the whole design.
In practice Spring WebFlux can be faster than a regular Spring MVC if the application workload is I/O bound e.g. a micro-service that calls multiple external APIs and doesn't perform significant calculations.
I'd suggest that you try simulating the I/O operations by making a network call to an actual server using a reactive library like Reactor Netty. Otherwise you would have to dig into the code and figure out how to create a meaningful mock for a network I/O operation, which might be tricky.
CodePudding user response:
Reactive programming is good for juggling work across threads when "someone else other than this JVM" is busy.
So, handover to OS for file write, or to DB for record update or to another remote server over API call. Let them call you back when they have the result. Eventually, you should be juggling, but work goes on somewhere else and results flow in. This is where reactivity shines.
So, it's difficult to simulate with Thread.sleep or for loop running 10_000 times.
Also, it means the whole flow has to be reactive - your disk IO library should be reactive, your DB library should be reactive, your Rest client for calling other networked services should be reactive. Reactor should be helping with solutions for each of these.
Also, its not all-or-nothing. Even if your disk IO is blocking, you will still gain benefits if atleast the Rest client is non-blocking.