为啥 Spring Webflux 只能并行接受 256 个请求?

Posted

技术标签:

【中文标题】为啥 Spring Webflux 只能并行接受 256 个请求?【英文标题】:Why does Spring Webflux only accepts 256 requests in parallel?为什么 Spring Webflux 只能并行接受 256 个请求? 【发布时间】:2019-03-27 12:00:54 【问题描述】:

在默认配置中,Spring Webflux 似乎将并行请求数限制为 256。

我的设置有这个非常简单的控制器:

@RestController
public class SimpleRestController 
  private final Log logger = LogFactory.getLog(getClass());

  private AtomicLong countEnter = new AtomicLong(0);
  private AtomicLong countExit = new AtomicLong(0);

  @GetMapping(value = "/delayed")
  public Mono<String> delayed() 
    logger.info("delayed ENTER " + countEnter.incrementAndGet());

    return Mono.just("result").delayElement(Duration.ofSeconds(60))

          .doOnNext(s -> logger.info("delayed EXIT " + countExit.incrementAndGet()));
  

配置只是启用WebFlux:

@SpringBootConfiguration
@EnableWebFlux
public class SearchServiceConfiguration 

依赖很少:

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>10</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.0</version>
        <configuration>
          <source>10</source>
          <target>10</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

我用这个脚本并行调用端点:

#!/usr/bin/env bash

for p in `seq 1 1000`;
do
    curl -s http://localhost:8080/delayed &
done

wait
echo "All done"

对于第 257 个请求,返回 Resource temporarily unavailable

哪个设置限制了并行请求的数量?

这是生成的日志输出:

2018-10-26 14:27:27.253  INFO 23728 --- [           main] s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "[/sleep],methods=[GET],produces=[application/stream+json]" onto public java.lang.String controller.SimpleRestController.sleep() throws java.lang.InterruptedException
2018-10-26 14:27:27.253  INFO 23728 --- [           main] s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "[/delayed],methods=[GET]" onto public reactor.core.publisher.Mono<java.lang.String> controller.SimpleRestController.delayed()
2018-10-26 14:27:27.315  INFO 23728 --- [           main] o.s.w.r.r.m.a.ControllerMethodResolver   : Looking for @ControllerAdvice: org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext@3cdf2c61: startup date [Fri Oct 26 14:27:26 CEST 2018]; root of context hierarchy
2018-10-26 14:27:27.639  INFO 23728 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-10-26 14:27:27.938  INFO 23728 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext     : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-10-26 14:27:27.939  INFO 23728 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2018-10-26 14:27:27.943  INFO 23728 --- [           main] d.h.s.s.SimpleApplication                : Started SimpleApplication in 1.548 seconds (JVM running for 2.396)
2018-10-26 14:27:34.295  INFO 23728 --- [ctor-http-nio-5] d.h.s.s.controller.SimpleRestController  : delayed ENTER 2
2018-10-26 14:27:34.295  INFO 23728 --- [ctor-http-nio-3] d.h.s.s.controller.SimpleRestController  : delayed ENTER 1
2018-10-26 14:27:34.295  INFO 23728 --- [ctor-http-nio-4] d.h.s.s.controller.SimpleRestController  : delayed ENTER 4
2018-10-26 14:27:34.295  INFO 23728 --- [ctor-http-nio-2] d.h.s.s.controller.SimpleRestController  : delayed ENTER 3
2018-10-26 14:27:34.324  INFO 23728 --- [ctor-http-nio-6] d.h.s.s.controller.SimpleRestController  : delayed ENTER 5
2018-10-26 14:27:34.351  INFO 23728 --- [ctor-http-nio-7] d.h.s.s.controller.SimpleRestController  : delayed ENTER 6
2018-10-26 14:27:34.381  INFO 23728 --- [ctor-http-nio-8] d.h.s.s.controller.SimpleRestController  : delayed ENTER 7
2018-10-26 14:27:34.413  INFO 23728 --- [ctor-http-nio-1] d.h.s.s.controller.SimpleRestController  : delayed ENTER 8
2018-10-26 14:27:34.438  INFO 23728 --- [ctor-http-nio-2] d.h.s.s.controller.SimpleRestController  : delayed ENTER 9
2018-10-26 14:27:34.466  INFO 23728 --- [ctor-http-nio-3] d.h.s.s.controller.SimpleRestController  : delayed ENTER 10
2018-10-26 14:27:34.497  INFO 23728 --- [ctor-http-nio-4] d.h.s.s.controller.SimpleRestController  : delayed ENTER 11
2018-10-26 14:27:34.526  INFO 23728 --- [ctor-http-nio-5] d.h.s.s.controller.SimpleRestController  : delayed ENTER 12
2018-10-26 14:27:34.561  INFO 23728 --- [ctor-http-nio-6] d.h.s.s.controller.SimpleRestController  : delayed ENTER 13
2018-10-26 14:27:34.594  INFO 23728 --- [ctor-http-nio-7] d.h.s.s.controller.SimpleRestController  : delayed ENTER 14
...
2018-10-26 14:27:42.675  INFO 23728 --- [ctor-http-nio-6] d.h.s.s.controller.SimpleRestController  : delayed ENTER 253
2018-10-26 14:27:42.711  INFO 23728 --- [ctor-http-nio-7] d.h.s.s.controller.SimpleRestController  : delayed ENTER 254
2018-10-26 14:27:42.745  INFO 23728 --- [ctor-http-nio-8] d.h.s.s.controller.SimpleRestController  : delayed ENTER 255
2018-10-26 14:27:42.774  INFO 23728 --- [ctor-http-nio-1] d.h.s.s.controller.SimpleRestController  : delayed ENTER 256
2018-10-26 14:28:04.312  INFO 23728 --- [     parallel-3] d.h.s.s.controller.SimpleRestController  : delayed EXIT 1
2018-10-26 14:28:04.312  INFO 23728 --- [     parallel-2] d.h.s.s.controller.SimpleRestController  : delayed EXIT 2
2018-10-26 14:28:04.321  INFO 23728 --- [     parallel-2] d.h.s.s.controller.SimpleRestController  : delayed EXIT 3
2018-10-26 14:28:04.321  INFO 23728 --- [     parallel-2] d.h.s.s.controller.SimpleRestController  : delayed EXIT 4
2018-10-26 14:28:04.333  INFO 23728 --- [     parallel-4] d.h.s.s.controller.SimpleRestController  : delayed EXIT 5
2018-10-26 14:28:04.359  INFO 23728 --- [     parallel-5] d.h.s.s.controller.SimpleRestController  : delayed EXIT 6
2018-10-26 14:28:04.389  INFO 23728 --- [     parallel-6] d.h.s.s.controller.SimpleRestController  : delayed EXIT 7
2018-10-26 14:28:04.421  INFO 23728 --- [     parallel-7] d.h.s.s.controller.SimpleRestController  : delayed EXIT 8
2018-10-26 14:28:04.446  INFO 23728 --- [     parallel-8] d.h.s.s.controller.SimpleRestController  : delayed EXIT 9
2018-10-26 14:28:04.474  INFO 23728 --- [     parallel-1] d.h.s.s.controller.SimpleRestController  : delayed EXIT 10
2018-10-26 14:28:04.505  INFO 23728 --- [     parallel-2] d.h.s.s.controller.SimpleRestController  : delayed EXIT 11
...
2018-10-26 14:28:12.570  INFO 23728 --- [     parallel-1] d.h.s.s.controller.SimpleRestController  : delayed EXIT 250
2018-10-26 14:28:12.607  INFO 23728 --- [     parallel-2] d.h.s.s.controller.SimpleRestController  : delayed EXIT 251
2018-10-26 14:28:12.643  INFO 23728 --- [     parallel-3] d.h.s.s.controller.SimpleRestController  : delayed EXIT 252
2018-10-26 14:28:12.683  INFO 23728 --- [     parallel-4] d.h.s.s.controller.SimpleRestController  : delayed EXIT 253
2018-10-26 14:28:12.719  INFO 23728 --- [     parallel-5] d.h.s.s.controller.SimpleRestController  : delayed EXIT 254
2018-10-26 14:28:12.752  INFO 23728 --- [     parallel-6] d.h.s.s.controller.SimpleRestController  : delayed EXIT 255
2018-10-26 14:28:12.782  INFO 23728 --- [     parallel-7] d.h.s.s.controller.SimpleRestController  : delayed EXIT 256

【问题讨论】:

听起来很奇怪。 Netty 不应该限制任何东西。这可能与操作系统有关。提供堆栈跟踪/错误/等。 没有堆栈跟踪。我使用 CommonsRequestLoggingFilter 开启了请求日志记录。但是第 257 个请求没有被记录。 你有另一台机器可以试一试吗? 不,我没有其他机器可以尝试。 附带说明,您不需要@EnableWebFlux,因为它覆盖了 Spring Boot 的所有自动配置。您可以安全地删除它。 【参考方案1】:

这不是我在本地设置中看到的。将您的控制器更改为以下内容确实会将“onNext 1”打印为“onNext 1000”:

AtomicLong count = new AtomicLong(0);

@GetMapping("/delayed")
public Mono<String> delayed() 
    return Mono.just("result").delayElement(Duration.ofSeconds(30))
            .doOnNext(s -> 
                logger.info("onNext " + count.incrementAndGet());
            );

在您的一个 cmets 中,您提到了 CommonsRequestLoggingFilter,它是特定于 Servlet 的。这告诉我你不是在运行 Spring WebFlux 应用程序,而是实际上是一个 Spring MVC 应用程序。如果您正在运行 Spring Boot,并且类路径中有 Spring MVC,Spring Boot will configure a Spring MVC application for you。

默认情况下,Spring MVC 将使用SimpleAsyncTaskExecutor 来调度这些请求的异步处理(Spring MVC 支持响应式返回类型,但会将它们视为@Async 请求,asynchronous but not non-blocking)。我认为SimpleAsyncTaskExecutor 默认没有并发限制。也许您在应用程序中配置了一个将并发限制为 256 的任务执行器?

我尝试使用 Spring MVC 应用程序重现该问题,但仍然处理了 999 个请求,并在日志中解决了一个异常:

.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]

【讨论】:

其实我并没有明确配置 SimpleAsyncTaskExecutor。那么 256 可以成为 SimpleAsyncTaskExecutor 的默认配置吗? 我将检查您关于 MVC 与 WebFlux 的链接,并查看依赖项中的切换是否会改变行为。 很难用一个小代码sn-p来弄清楚这里发生了什么。甚至 MVC 与 WebFlux 也是来自您的一个 cmets 的有根据的猜测。提供有关您的应用程序的更多信息应该会有所帮助。 @tk,SimpleAsyncTaskExecutor,默认情况下用于 Spring 中的异步处理,为每个任务生成一个新线程。每个线程都需要内存。您专用于 JVM 的内存很可能仅足以创建 256 个线程。您可以减少堆栈大小或增加 JVM 内存来确定是否是问题所在。使用–Xss512k JVM 选项将堆栈大小减少一半,并查看是否有 512 个线程而不是 256 个。 @tkr 请使用构建文件或依赖项列表更新您的问题。在不编辑原始问题的情况下在 cmets 中提供提示会使愿意帮助您的 SO 贡献者更难。【参考方案2】:

服务器不是您的限制因素。该错误表明由于资源限制,创建后台进程失败。有几个可能的原因:

内存 - 每个进程都需要内存来加载 curl。 进程 - 您可以创建的进程数量可能存在限制 打开文件 - 每个进程打开多个“文件”。那是引号,因为“文件”包括网络连接和 curl 加载的动态库。一次可以打开的数量在系统范围内有限制。

【讨论】:

谢谢。实际上日志资源暂时不可用与服务器无关。 256 个请求的限制是由我非常简单的 shell 脚本造成的。 @tkr 你能否进一步解释一下为什么问题与你的 shell 脚本有关?

以上是关于为啥 Spring Webflux 只能并行接受 256 个请求?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 webflux 进行 spring boot 测试会忽略自定义 jackson 模块

为啥spring webflux默认选择jetty然后失败?

为啥默认配置的spring webflux中没有异常堆栈跟踪?

为啥 Spring WebFlux 应用程序不支持 @RestController 映射前缀?

Spring WebFlux,提取请求体

webFlux 学习