This article will compare some applications to Spring MVC and Spring WebFlux, observe the differences in the threading model, and then do a simple stress test.

Create a traditional Spring MVC application

To create a new webflux-MVC module:

<? xml version="1.0" encoding="UTF-8"? > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> < modelVersion > 4.0.0 < / modelVersion > < groupId > me. Josephzhu < / groupId > < artifactId > spring101 webflux - MVC < / artifactId > < version > 0.0.1 - the SNAPSHOT < / version > < packaging > jar < / packaging > < name > spring101 webflux - MVC < / name > <description></description> <parent> <groupId>me.josephzhu</groupId> <artifactId>spring101</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>Copy the code

Then define a POJO in the project that we will use:

package me.josephzhu.spring101webfluxmvc;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "mydata")
public class MyData {
    @Id
    private String id;
    private String payload;
    private long time;
}
Copy the code

Here @document and @ID are for Mongodb service. We define MyData as the name of Collection, and then the Id field is the Id column of Document. Then we create the Controller, and in this Controller we try three different operations:

  1. Sleep 100ms pure data acquisition method. Get the length parameter from the request as the length of the payload string, and the size parameter from the request as the number of MyData. We can adjust these two parameters at will to adjust the amount of data in the subsequent test.
  2. A method to obtain data from Mongodb and return the obtained data directly.
  3. Compound logic. Get the data from the data method using HTTP requests, save the data to Mongodb, and return the data.
package me.josephzhu.spring101webfluxmvc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@RestController
public class MyController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private MyRepository myRepository;

    @GetMapping("/data")
    public List<MyData> getData(@RequestParam(value = "size", defaultValue = "10") int size,@RequestParam(value = "length", defaultValue = "100") int length) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {

        }
        String payload = IntStream.rangeClosed(1,length).mapToObj(i->"a").collect(Collectors.joining());
        return IntStream.rangeClosed(1, size)
                .mapToObj(i->new MyData(UUID.randomUUID().toString(), payload, System.currentTimeMillis()))
                .collect(Collectors.toList());
    }

    @GetMapping("/dbData")
    public List<MyData> getDbData(a) {
        return myRepository.findAll();
    }

    @GetMapping("/saveData")
    public List<MyData> saveData(@RequestParam(value = "size", defaultValue = "10") int size,@RequestParam(value = "length", defaultValue = "100") int length){
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/data")
                .queryParam("size", size)
                .queryParam("length", length);
        ResponseEntity<List<MyData>> responseEntity =
                restTemplate.exchange(builder.toUriString(),
                        HttpMethod.GET, null.new ParameterizedTypeReference<List<MyData>>() {});
        returnresponseEntity.getBody().stream().map(myRepository::save).collect(Collectors.toList()); }}Copy the code

Note that here we use Steam for Java 8 to do something to avoid using the for loop:

  1. Construct the payload using the length parameter. The payload consists of the length character A.
  2. Build the List of MyData with the size argument.
  3. After RestTemplate retrieves the List of MyData, each object is handed over to myRepository’s Save method for processing, and the results are collected uniformly.

The code for these streams is synchronous, does not involve external IO, and has nothing to do with non-blocking. To make the code work, we need to go ahead and configure Mongodb’s Repository:

package me.josephzhu.spring101webfluxmvc;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MyRepository extends MongoRepository<MyData.String> {}Copy the code

Because we’re not using a complex query, we’re just using the findAll method in our code, so we don’t need to define additional methods here, just declare the interface. Finally, we create the main application and configure Mongodb and RestTemplate:

package me.josephzhu.spring101webfluxmvc;

import com.mongodb.MongoClientOptions;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@Configuration
public class Spring101WebfluxMvcApplication {

   @Bean
   MongoClientOptions mongoClientOptions(a){
       return MongoClientOptions.builder().connectionsPerHost(1000).build();
   }

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }

    public static void main(String[] args) { SpringApplication.run(Spring101WebfluxMvcApplication.class, args); }}Copy the code

Here we have configured the Mongodb client so that more than 100 connections can be connected to Mongodb later in the stress test, otherwise we will not be able to get connections.

Create a WebFlux version of the application

Now let’s create a new WebFlux module:

<? xml version="1.0" encoding="UTF-8"? > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> < modelVersion > 4.0.0 < / modelVersion > < groupId > me. Josephzhu < / groupId > < artifactId > spring101 - webflux < / artifactId > < version > 0.0.1 - the SNAPSHOT < / version > < packaging > jar < / packaging > < name > spring101 - webflux < / name > < description > < description > Josephzhu </groupId> <artifactId>spring101</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Copy the code

Note that we have introduced webflux as a starter and data-mongodb-Reactive as a starter. In the previous Spring MVC project, we introduced MVC and data-mongodb as starter. Next, we also need to create the MyData class (the code is the same as before, but omitted here). For the most critical step, let’s create three Controller method definitions:

package me.josephzhu.spring101webflux;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.springframework.web.reactive.function.server.ServerResponse.ok;

@Component
public class MyHandler {
    @Autowired
    private MyReactiveRepository myReactiveRepository;

    public Mono<ServerResponse> getData(ServerRequest serverRequest) {
        int size = Integer.parseInt(serverRequest.queryParam("size").orElse("10"));
        int length = Integer.parseInt(serverRequest.queryParam("length").orElse("100"));

        String payload = IntStream.rangeClosed(1,length).mapToObj(i->"a").collect(Collectors.joining());
        Flux<MyData> data = Flux.fromStream(IntStream.rangeClosed(1, size)
                .mapToObj(i->new MyData(UUID.randomUUID().toString(), payload, System.currentTimeMillis()))).delaySequence(Duration.ofMillis(100));

        return ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(data, MyData.class);
    }

    public Mono<ServerResponse> getDbData(ServerRequest serverRequest) {
        Flux<MyData> data = myReactiveRepository.findAll();
        return ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(data, MyData.class);
    }

    public Mono<ServerResponse> saveData(ServerRequest serverRequest) {
        int size = Integer.parseInt(serverRequest.queryParam("size").orElse("10"));
        int length = Integer.parseInt(serverRequest.queryParam("length").orElse("100"));

        Flux<MyData> data = WebClient.create().get()
                .uri(builder -> builder
                        .scheme("http")
                        .host("localhost")
                        .port(8080)
                        .path("data")
                        .queryParam("size", size)
                        .queryParam("length". length) .build()) .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToFlux(MyData.class) .flatMap(myReactiveRepository::save);returnok() .contentType(MediaType.APPLICATION_JSON) .body(data, MyData.class); }}Copy the code

Here are a few points:

  1. In WebFlux, Controller can be defined in the traditional @controller way, or the external Endpoint can be declared in a functional way, that is, Handler+Router. Here we use the more characteristic of the latter to demonstrate.
  2. Compare the implementation of the three methods to the two versions. The main difference is that the actual data we return are Mono<> and Flux<>, which represent the reactive flow of 0~1 objects and 0~N objects respectively.
  3. In the saveData method, we use a blocking RestTemplate for Spring MVC to fetch data from the remote end, and a non-blocking WebClient for Spring WebFlux to fetch data. After data acquisition, we directly obtained all MyData using flatMap and transferred it to our responsive Mongodb Repository for data processing.
  4. There are significant differences between this and the MVC example for inserting Mongodb in the saveData method. In MVC, we convert the result returned by the remote service into a Stream and synchronously call the save method in turn, the whole process will occupy only one Mongodb connection. In this case, the Flux flow is directly mapped, which is equivalent to a concurrent Mongodb call. We’ll come back to this later when we do the manometry.

In addition to defining Handler, you also need to configure the Router to associate it with Handler. The configuration is as follows:

package me.josephzhu.spring101webflux;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration
public class RouterConfig {
    @Autowired
    private MyHandler myHandler;

    @Bean
    public RouterFunction<ServerResponse> config(a) {
        return route(GET("/data"), myHandler::getData)
                .andRoute(GET("/dbData"), myHandler::getDbData)
                .andRoute(GET("/saveData"), myHandler::saveData); }}Copy the code

This code doesn’t need much explanation, here we define three GET requests (equivalent to @getMapping for MVC) that correspond to the three methods of the injected myHandler. Then we need to create Mongodb’s Repository:

package me.josephzhu.spring101webflux;

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MyReactiveRepository extends ReactiveMongoRepository<MyData, String> { }
Copy the code

And configuration and startup classes:

package me.josephzhu.spring101webflux;

import com.mongodb.ConnectionString;
import com.mongodb.async.client.MongoClientSettings;
import com.mongodb.connection.ClusterSettings;
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@SpringBootApplication
@Configuration
public class Spring101WebfluxApplication {

    @Bean
    MongoClient mongoClient(a){
        return MongoClients.create(mongoClientSettings());
    }

    @Bean
    MongoClientSettings mongoClientSettings(a){
        return MongoClientSettings.builder()
                .clusterSettings(ClusterSettings.builder().applyConnectionString(new ConnectionString("mongodb://localhost")).build())
                .connectionPoolSettings(ConnectionPoolSettings.builder().minSize(200).maxSize(1000).maxWaitQueueSize(1000000).build())
                .build();
    }

    public static void main(String[] args) { SpringApplication.run(Spring101WebfluxApplication.class, args); }}Copy the code

Some configuration is done to Mongodb, mainly to enlarge the default limit of connection pooling for future pressure testing. Note that configuration Bean is com here. The mongo. Reactivestream. MongoClient under the client, as shown in the figure below, there are other two MongoClient, if changed the mismatch MongoClient is not useful, I lay in this hole for two hours.

Spring MVC or WebFlux?

Below is an illustration from the website to illustrate the relationship between the two, and the website also offers some advice:

  1. If your current Spring MVC doesn’t have any problems, don’t change it, there are plenty of libraries to work with and the implementation is simple and easy to understand.
  2. If you want to implement a lightweight, functional Web framework, consider WebFlux’s functional Web endpoints.
  3. If you rely on blocking persistence apis such as JPA and JDBC then Spring MVC is the only option. There are some early projects exploring non-blocking JDBC implementations, but they are not yet ready for production.
  4. It is also possible to make remote calls within Spring MVC applications using responsive WebClient. Spring MVC can also use other responsive components. The greater the delay per call, the greater the benefit.
  5. Consider the learning curve of non-blocking implementations for large applications. The easiest way to get started is with WebClient, and switching to non-blocking completely takes time to become familiar with functional declarative programming apis.

    The official message is that you can try WebFlux on small references, but switching to WebFlux is not recommended for large applications.

Observe thread model

We know for blocking the implementation of the way, we use a thread pool to service requests (can maintain a set of common threads in thread pool, pool just save thread creation time), for processing each request, throughout is conducted in a thread, if handled in the process of we need to access external network or database, The thread is blocked, the thread can’t serve other requests, and if there are more concurrent requests, more threads need to be created to serve other requests. This implementation is very simple, and the way to deal with stress is to add more threads.

In the non-blocking mode, EventLoop is used, which does not use worker threads for IO operations, so only a set of worker threads equal to the number of CPU cores are created for the processing of work (NodeJS or even single-threaded, which is even more dangerous, just one worker thread). If it is occupied for a long time, other requests cannot be processed. Since I/O requests don’t take up thread time during the entire process and the thread doesn’t block and wait, it doesn’t make sense to add more worker threads than the number of CPU cores (just adding thread switching overhead). With this approach, there is no absolute bottleneck because we don’t need to add additional threads after the pressure increases.

Imagine under the block model, for 5000 concurrent, and each of the concurrent blocking time is very long, so we really need 5000 threads to service (so multithreaded 99% are waiting for, belong to the waste of system resources), to create 5000 thread does not talk about other, if the thread stack size is 1 m requires 5 gb of memory. For a non-blocking thread model with eight worker threads on an 8-core machine, the memory footprint is still so small that large concurrency can be handled with minimal overhead and minimal system wear. Non-blocking Reactive mode is a very low-consumption mode, but it comes at a cost. In implementation, we need to ensure that no blocking occurs during processing, otherwise we waste a valuable number of fixed worker threads, which means that we need to rely on the supporting non-blocking IO libraries to use it. By default tomcat worker thread pool initialized to 10, 200, we started Spring101WebfluxMvcApplication program created in this paper, using jvisualvm tools to look at the situation of the initial thread (35) :

Stress tests were performed using Gatling

We can use the Gatling class library for stress testing, which I personally find more convenient than Jmeter. The configuration is simple. First we install Scala’s SDK, and then we create a new module:

<? xml version="1.0" encoding="UTF-8"? > <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> < modelVersion > 4.0.0 < / modelVersion > < groupId > me. Josephzhu < / groupId > < artifactId > spring101 - webstresstest < / artifactId > < version > 0.0.1 - the SNAPSHOT < / version > < packaging > jar < / packaging > < name > spring101 - webstresstest < / name > <description></description> <dependencies> <dependency> <groupId>io.gatling.highcharts</groupId> <artifactId>gatling-charts-highcharts</artifactId> <version>2.3.1</version> </dependency> </dependencies> </dependencies> <build> Plugins > <plugin> <groupId> IO. Gatling </groupId> <artifactId>gatling-maven-plugin</artifactId> <version>2.2.4</version> <configuration> <simulationClass>me.josephzhu.spring101.webstresstest.StressTest</simulationClass> <resultsFolder>/Users/zyhome/gatling</resultsFolder> </configuration> </plugin> </plugins> </build> </project>Copy the code

Garling’s Maven plugin was introduced, where the test result output path and the pressure test classes are configured. Next, create the Scala test class:

package me.josephzhu.spring101.webstresstest

import io.gatling.core.Predef. _import io.gatling.core.scenario.Simulation
import io.gatling.http.Predef. _class StressTest extends Simulation {

  val scn = scenario("data").repeat(1000) {
    exec(
      http("data")
        .get("http://localhost:8080/data? size=10&length=1000")
        .header("Content-Type"."application/json")
        .check(status.is(200)).check(substring("payload")))
  }

  setUp(scn.inject(atOnceUsers(200)))}Copy the code

This code defines the following test behavior:

  1. Declare a data test scenario, repeat the test 1000 times, make a remote call, verify that the response status code of the call result is 200 and return the result containing the string payload.
  2. The test starts with 200 users, and each user runs 1000 tests and then it’s over, so it starts with 200 users and then it goes down to the end of the test. Of course, there are other testing methods (such as gradually increasing the number of users), which can be found on the website:
    nothingFor(4 seconds), // 1
    atOnceUsers(10), // 2
    rampUsers(10) over (5 seconds), // 3
    constantUsersPerSec(20) during (15 seconds), // 4
    constantUsersPerSec(20) during (15 seconds) randomized, // 5
    rampUsersPerSec(10) to 20 during (10 minutes), // 6
    rampUsersPerSec(10) to 20 during (10 minutes) randomized, // 7
    splitUsers(1000) into (rampUsers(10) over (10 seconds)) separatedBy (10 seconds), // 8
    splitUsers(1000) into (rampUsers(10) over (10 seconds)) separatedBy atOnceUsers(30), // 9
    heavisideUsers(1000) over (20 seconds) // 10
Copy the code

Stress Test 1

Let’s start with the first test, 1000 concurrent loops through the data interface 100 times (remember, interfaces have 100ms sleep or latency) :

class StressTest extends Simulation {

  val scn = scenario("data").repeat(100) {
    exec(
      http("mvc data")
        .get("http://localhost:8080/data? size=10&length=1000")
        .header("Content-Type"."application/json")
        .check(status.is(200)).check(substring("payload")))
  }

  setUp(scn.inject(atOnceUsers(1000)))}Copy the code

The following two graphs show the test results of MVC and WebFlux respectively (since they are both port 8080, remember to switch and restart the two applications during the test) :

Performance Test 2

Now we come to visit http://localhost:8080/saveData? Class StressTest extends Simulation {class StressTest extends Simulation {class StressTest extends Simulation {

val scn = scenario(“data”).repeat(100) { exec( http(“data”) .get(“http://localhost:8080/dbData”) .header(“Content-Type”, “application/json”) .check(status.is(200)).check(substring(“payload”))) }

SetUp (scn.inject(atOnceUsers(1000)))}

Performance Test 3

Try the third saveData interface again. Modify the test code:

class StressTest extends Simulation {

  val scn = scenario("data").repeat(100) {
    exec(
      http("data")
        .get("http://localhost:8080/saveData? size=5&length=100000")
        .header("Content-Type"."application/json")
        .check(status.is(200)).check(substring("payload")))
  }

  setUp(scn.inject(atOnceUsers(200)))}Copy the code

Here, we change the number of concurrent users to 200, and conduct 100 tests for each user. Each test stores 5 pieces of 100KB data into Mongodb, and the total amount of data after one test is 100,000 pieces. In this test, we did not use 1000 concurrent users, because in this test, we will first obtain data from the remote end and then store it to Mongodb. The remote service also comes from the current application. Our Tomcat has only 250 threads at most, and after 1000 users are started, some threads serve saveData interface. Some threads serve the Data interface (used by the saveData interface), which is equivalent to a circular dependency problem. Requests are waiting for more available threads to execute the response from the data interface, and at this time, threads are occupied, so that no more requests can be allocated, and almost all tests time out. Here are the test results, MVC and WebFlux respectively:

Performance Test 4:

Let’s test the following two scenarios for the Mongodb side of the WebFlux version:

class StressTest extends Simulation {

  val scn = scenario("data").repeat(1000) {
    exec(
      http("data")
        .get("http://localhost:8080/saveData? size=1&length=1000")
        .header("Content-Type"."application/json")
        .check(status.is(200)).check(substring("payload")))
  }

  setUp(scn.inject(atOnceUsers(200)))}Copy the code

As well as

class StressTest extends Simulation {

  val scn = scenario("data").repeat(1000) {
    exec(
      http("data")
        .get("http://localhost:8080/saveData? size=5&length=1000")
        .header("Content-Type"."application/json")
        .check(status.is(200)).check(substring("payload")))
  }

  setUp(scn.inject(atOnceUsers(200)))}Copy the code

The difference is whether the remote service returns 1 Flux or 5. If we run the test at 1, we can see that Mongodb has 64 connections (we need to set the connection pool configuration to a smaller minimum, say 50) :

> db.serverStatus().connections
{ "current" : 64, "available" : 3212, "totalCreated": 8899}Copy the code

When size is 5, Flux returns 5 objects. When using this request to pressure test, Mongodb connections are as follows:

> db.serverStatus().connections
{ "current" : 583, "available" : 2693, "totalCreated": 10226}Copy the code

This is because Flux gets the data directly into Mongodb in response, rather than waiting until all the data is available to call the methods in sequence. Summarizing these tests, we found that WebFlux has a slight performance improvement over MVC and a significant performance advantage over request blocking. My principal tests did not see several times or even dozens of times of performance improvement, and I guess the reasons are as follows:

  1. The machine has performance bottleneck, pressure test client, Mongodb server, server are all running on the machine, interference factors are too much, THE USE of CPU is competing, the test is unfair
  2. The CPU was always 100% and crashed several times during the test, and I was unable to test for higher concurrency and fully squeeze out non-blocking performance
  3. I used localhost instead of Intranet in my local test, which may not show non-blocking performance without going through physical network cards

More scientific results may be obtained if three independent servers can be used to test the performance of more than 10,000 concurrent users on the Intranet.

conclusion

In this paper, we created two sets of applications, WebFlux and MVC, and demonstrated some simple application scenarios, such as simply returning data, sending remote requests, and using Mongodb. Then we looked at the differences between ThreadPerRequest and EventLoop thread models. Finally, Gatling was used to conduct several Case stress tests and observe the results. I think:

  1. The non-blocking model is definitely a good thing. In the case of high IO pressure and IO latency, the non-blocking model is better for high concurrency because it doesn’t require more threads, has lower internal consumption, slightly better performance, and is also stable
  2. The functional and declarative implementation of WebFlux requires a high threshold of API familiarity. For complex logic, the implementation of this method is easier to get around than callback hell, and it is prone to bugs (perhaps reactive programming may be unified with traditional methods in API in the future).
  3. At present, some other Reactive libraries supporting WebFlux are not fully mature, and it is a little difficult to fully enable Reactive programming for complex business logic. Blocking calls can not be mixed in WebFlux, but this method still adopts thread pool. Now the container is NIO, so it makes a big difference
  4. A blocking way, natural back pressure by blocked thread for flow control, non-blocking way is a pole directly to the end, from the external request directly to the bottom of the storage, need to be flow control, it’s very easy to cause problems of a point, when the request processing need not through the thread to host, front end pressure will direct the bottom data sources, Don’t accept any expansion restrictions, directly destroy the bottom
  5. As for the blocking method, multi-threaded scheduling is a natural load balancing task, and there is no serious problem of worker thread jamming. In non-blocking application programming, we need to be aware of which thread the code is running on, and must not block for a long time if reactor thread

To sum up, I personally think that using WebFlux reactive programming is only suitable for class IO forward high concurrency and take a fancy to resource use efficiency of application scenarios (for example, the Gateway Gateway service), for complex business logic is not very fit, in 90% of cases reactive programming model and threading model doesn’t enjoy big performance advantages, It is not recommended to blindly rewrite existing applications using WebFlux. Of course, this is definitely a direction that will continue to develop, so we can contact and study it first.