如何利用 Spring Web MVC 控制器中的非阻塞请求

How to take advantage of non-blocking requests in Spring Web MVC Controllers

我正在尝试展示在 Spring MVC 中使用 Reactive Streams 的优势。为此,我有一个小型 Jetty 服务器 运行 两个端点:

然后我启动一个客户端并在其中一个端点同时发起几千个请求。我希望在第二个端点看到更少的错误,处理是异步发生的。但是,我有时会在启用异步的端点上观察到 more 错误;在这两种情况下,Connection refused: no further information.

的 60 - 90% 误差之间的任何地方

要么是我哪里做错了,要么是我不太明白。 Connection refused 正是我希望避免的事情。

服务器

这是我来自服务器的代码。在 normal 的情况下,我确实用 .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);
    }
}

服务器通过 Maven 在 Jetty 9.4.15 上 运行 并且 web.xml 定义为:

<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">

客户

我的客户端使用 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;
    }

}

与此处问题类似的前提:WebFlux async processing。主要区别是我测试的是错误率,或者同步连接数;我不希望速度提高。

事实证明,虽然 Servlet 3.1 规范支持 non-blocking IO,但 Spring MVC 不支持。要充分利用响应式 API,您必须使用 WebFlux。有关详细信息,请参阅此处:https://youtu.be/Dp_aJh-akkU?t=1738.

此图演示了 Spring MVC(左侧)与 Webflux(右侧)的工作原理。

我使用 Gatling 进行了更多测试并得到了相似的结果:两者花费的时间大致相同,异步稍微不可靠。但是,我确实注意到一个 semi-reproducable 区别:异步结果有时响应速度更快:

正常

第 50 个百分位数:33.6 秒 第 95 个百分位数:35.4 秒

被动

第 50 个百分位数:6.51 秒 第 95 个百分位数:49.5 秒

我仍然不清楚在 Spring MVC 中使用异步调用(例如 DeferredResult)或反应流 API 的优势。因此,如果有人能够用具体的用例来阐明这一点,我们将不胜感激。