2

I am trying to demonstrate the advantages of using Reactive Streams in Spring MVC. For that I have a small Jetty server running with two end-points:

  • /normal returns a POJO
  • /flux returns the same object wrapped in a Mono

Then I start a client and launch a few thousand simultaneous requests at one of these end-points. I would have expected to see fewer errors with the second end-point, where the processing happens asynchronously. However, I sometimes observe more errors on the async-enabled endpoint; in both cases, anywhere between 60 - 90 % errors of Connection refused: no further information.

Either I'm doing something wrong here or I don't quite understand. Connection refused is just the kind of thing I would expect to avoid.

Server

Here is my code from the server. In the normal case I literally block the thread with a .sleep():

@Controller
public class FluxController {
    @GetMapping(value = "/normal", produces = MediaType.APPLICATION_JSON_VALUE)
    public Map normal() throws Exception {
        Thread.sleep(randomTime());
        return Collections.singletonMap("type", "normal");
    }

    @GetMapping(value = "/flux", produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<Map> flux() {
        return Mono.delay(Duration.ofMillis(randomTime()))
                .map(x -> Collections.singletonMap("type", "flux"));
    }

    private static long randomTime() {
        return ThreadLocalRandom.current().nextLong(200, 1000);
    }
}

The server is running on Jetty 9.4.15 through Maven and the web.xml is defined with:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">

Client

My Client uses Spring WebClient:

public class ClientApplication {

    private static final String ENDPOINT = "normal";

    private static final int REPETITIONS = 10_000;

    public static void main(String[] args) {
        WebClient client = WebClient.create("http://localhost:8080");

        AtomicInteger errors = new AtomicInteger(0);

        List<Mono<Response>> responses = IntStream.range(0, REPETITIONS)
                .mapToObj(i -> client.get()
                        .uri(ENDPOINT)
                        .retrieve()
                        .bodyToMono(Response.class)
                        .doOnError(e -> errors.incrementAndGet())
                        .onErrorResume(e -> Mono.empty())
                )
                .collect(Collectors.toList());
        Mono.when(responses)
                .block();
        System.out.println(String.format("%-2f %% errors", errors.get() * 100.0 / REPETITIONS));
    }

    static class Response {
        public String type;
    }

}

A similar premise to the question here: WebFlux async processing. The main difference is that I am testing the error rate, or the number of synchronous connections; I don't expect a speed increase.

Druckles
  • 3,161
  • 2
  • 41
  • 65
  • Spring MVC and Spring Webflux are different technologies. Which one do you use? What exactly do you want to prove? Servlet 3.0 only supports blocking I/O btw. – a better oliver May 11 '19 at 09:15
  • Technically this doesn't have anything to do with WebFlux. Or even Project Reactor. The same effect is achieved by returning Deferred results. And this is running on Servlet 3.1. I've updated the tags to reflect this. – Druckles May 11 '19 at 10:54
  • With Spring MVC returning a `Mono` and returning a `DeferredResult` is more or less the same. Webflux, however, works differently. So it makes a difference which one you use. Async processing might introduce an overhead in your test scenario and nullify its potential advantage. The CPU could spend too much time on thread switching. – a better oliver May 13 '19 at 20:31

1 Answers1

2

It turns out that although the Servlet 3.1 specs support non-blocking IO, Spring MVC does not. To take full advantage of the reactive API, you must use WebFlux. See here for more information: https://youtu.be/Dp_aJh-akkU?t=1738.

This image demonstrates how Spring MVC (on the left) works compared to Webflux (on the right).

Spring MVC vs Webflux Stack

I performed some more tests using Gatling and had similar results: both took approximately the same amount of time, async was slightly less reliable. However, I did notice one semi-reproducable difference: the async results were sometimes quicker to respond:

Normal

Response Time Distribution when returning standard type

50th percentile: 33.6 s 95th percentile: 35.4 s

Reactive

Response Time Distribution when returning Flux

50th percentile: 6.51 s 95th percentile: 49.5 s

I'm still not clear on the advantages of using Async calls (e.g. DeferredResult) or the Reactive Streams API in Spring MVC. So if anyone is able to clarify this with concrete use cases it would be greatly appreciated.

Druckles
  • 3,161
  • 2
  • 41
  • 65
  • This presentation discusses using the WebClient with Spring MVC and shows some concrete examples: https://www.infoq.com/presentations/spring-reactive-webflux/ Also this section of the documentation discusses some differences and similarities: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-framework-choice – peater Apr 22 '20 at 17:55