【问题标题】:How to take advantage of non-blocking requests in Spring Web MVC Controllers如何利用 Spring Web MVC 控制器中的非阻塞请求
【发布时间】:2019-05-10 14:34:26
【问题描述】:

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

  • /normal 返回一个 POJO
  • /flux 返回包装在 Mono 中的相同对象

然后我启动一个客户端并在其中一个端点同时启动几千个请求。我本来希望看到第二个端点的错误更少,处理是异步发生的。但是,我有时会在启用异步的端点上观察到 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。主要区别在于我正在测试错误率,或同步连接数;我预计速度不会提高。

【问题讨论】:

  • Spring MVC 和 Spring Webflux 是不同的技术。你用哪一个?你到底想证明什么? Servlet 3.0 仅支持阻塞 I/O。
  • 技术上这与 WebFlux 没有任何关系。甚至是项目反应堆。通过返回 Deferred 结果可以达到相同的效果。这是在 Servlet 3.1 上运行的。我已经更新了标签以反映这一点。
  • Spring MVC 返回 Mono 和返回 DeferredResult 或多或少是相同的。然而,Webflux 的工作方式不同。因此,使用哪一个会有所不同。异步处理可能会在您的测试场景中引入开销并抵消其潜在优势。 CPU 可能会在线程切换上花费太多时间。

标签: java spring-mvc project-reactor servlet-3.1


【解决方案1】:

事实证明,虽然 Servlet 3.1 规范支持非阻塞 IO,但 Spring MVC 不支持。要充分利用反应式 API,您必须使用 WebFlux。更多信息请看这里:https://youtu.be/Dp_aJh-akkU?t=1738

这张图片展示了 Spring MVC(左侧)与 Webflux(右侧)相比的工作原理。

我使用Gatling 进行了更多测试,得到了相似的结果:两者花费的时间大致相同,异步的可靠性稍差。但是,我确实注意到了一个半可重现的差异:异步结果有时响应更快:

正常

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

反应性

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

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-25
  • 1970-01-01
  • 2012-12-24
  • 2013-11-12
  • 2018-05-29
  • 1970-01-01
  • 2021-08-28
相关资源
最近更新 更多