Hello, I'm Mitz. This is the first post on dev.to. Nice to meet you :)
Buzzwords into my ToolBox
As many of you've heard Microservices, Blockchain, etc, there're a variety of tech buzzwords and they come and go often. Therefore, it's important for us not to believe them without even checking, but to see for ourselves how they work then put them into our toolbox. That's why I tried using a part of Reactive Programming this time.
One of the benefit of Reactive Programming is that we can use machine resources effectively. For example, in case of a web application, a server can handle more requests than blocking style application with less threads.
SpringBoot2 Reactive Web
SpringBoot2, which was released at the beginning of this month, has introduced "Reactive Web" feature. So I tried comparing "Spring Web" and "Spring Reactive Web". "Spring Web" is based on a conventional blocking style with Servlet, and "Spring Reactive Web" is a new style with reactive programming.
(Image from https://spring.io/ )
Demo Apps Architecture
I found a great article which compares SpringBoot1 and SpringBoot2:
Raw Performance Numbers - Spring Boot 2 Webflux vs. Spring Boot 1.
I did almost the same thing to compare SpringBoot2 Blocking Web vs Reactive Web. The architecture is like this:
There're 3 apps:
- delay-service
- blocking-app
- reactive-app
The source code is here:
https://github.com/bufferings/webflux-demo-201803
delay-service
The delay-service emulates an outside API with some latency. We can set the latency with a path parameter:
@GetMapping("/{delayMillis}")
public Mono<String> get(@PathVariable int delayMillis) {
return Mono.just("OK")
.delayElement(Duration.ofMillis(delayMillis));
}
blocking-app
The blocking-app is a simple Spring Web app, which calls delay-service in a blocking manner and returns it with a blocking manner:
private static final String DELAY_SERVICE_URL = "http://localhost:8080";
private final RestTemplate client;
public BlockingApp(RestTemplateBuilder builder) {
client = builder.rootUri(DELAY_SERVICE_URL).build();
}
@GetMapping("/{delayMillis}")
public String get(@PathVariable String delayMillis) {
String result = client.getForObject("/" + delayMillis, String.class);
return "Blocking:" + result;
}
reactive-app
The reactive-app is a Spring Web Reactive app, which calls delay-service with a reactive client and returns Mono
:
private static final String DELAY_SERVICE_URL = "http://localhost:8080";
private final WebClient client = WebClient.create(DELAY_SERVICE_URL);
@GetMapping("/{delayMillis}")
public Mono<String> get(@PathVariable String delayMillis) {
return client.get()
.uri("/" + delayMillis)
.retrieve()
.bodyToMono(String.class)
.map(s -> "Reactive:" + s);
}
Let's check the performance!
Start delay-service:
./gradlew -p apps/delay-service clean bootRun
curl -w "\n%{time_total}s\n" localhost:8080/1000
# returns "OK" after 1000ms
curl -w "\n%{time_total}s\n" localhost:8080/2000
# returns "OK" after 2000ms
Start blocking-app:
./gradlew -p apps/blocking-app clean bootRun
curl -w "\n%{time_total}s\n" localhost:8081/2000
# returns "Blocking:OK" after 2000ms
Start reactive-app:
./gradlew -p apps/reactive-app clean bootRun
curl -w "\n%{time_total}s\n" localhost:8082/2000
# returns "Reactive:OK" after 2000ms
Now, all three apps are running.
Load Test Scenario
I used Gatling(https://gatling.io/) for the load test.
The scenario is something like this "1000 users call the API 30 times with 1 to 2 sec intervals". I would like to set the latency of delay-service as 300ms.
val myScenario = scenario("Webflux Demo").exec(
repeat(30) {
exec(
http("request_1").get(targetUrl)
).pause(1 second, 2 seconds)
}
)
setUp(myScenario.inject(rampUsers(simUsers).over(30 seconds)))
Gatling to blocking-app
./gradlew -p apps/load-test -DTARGET_URL=http://localhost:8081/300 \
-DSIM_USERS=1000 gatlingRun
With VisualVM, we can see the worker threads count increase up to 200 which is the default maxThread value of the Tomcat.
Gatling to reactive-app
./gradlew -p apps/load-test -DTARGET_URL=http://localhost:8082/300 \
-DSIM_USERS=1000 gatlingRun
It only use 4 threads to handle the request.
Load Test Result
Here's the result.
As you can see, for 1000 users both apps work nicely with around 300ms response time as we expected. But for 3000 & 6000 users, the 95 percentile of blocking-app becomes worse.
On the other hand, reactive-app keeps the good response speed around 400ms and it shows about 2000rps with my laptop(Core i7-7500U 2.7GHz/16GB RAM).
Interesting result!
blocking-app
reactive-app
What's left
Increasing Tomcat maxThreads
Since I decided to try with the default config, the Tomcat threads count reached to 200 which is the default value of maxThreads. Probably tuning maxThread would improve the blocking-app performance.
Separating the env
Since I tried this demo all in my laptop, all the apps affected each other regarding the resource usage. So separating each apps into several machine might show different result.
Conclusion
In conclusion, we could know how SpringBoot2 Reactive Web handles requests efficiently. But I recommend you to check it by yourself. Especially Reactive Web style programming requires Java engineer to change their mindset to some extent I think.
Anyway, it's so interesting and I'm feeling now SpringBoot2 Reactive Web is in my toolbox. Thank you!
Latest comments (0)