目录

一、项目搭建

二、验证最大并发数

三、源码分析

        3.1 Tomcat 核心配置讲解


        Spring 生态的重要性不用多说,Spring Boot 已经成为 Java 后端开发的"标准",但是一个Spring Boot 项目到底能同时应对多少请求呢?你有没有考虑过这个问题呢?这时你可能会问,处理的业务是什么?服务的配置是什么样的?使用的 WEB 容器是什么等等问题,当然我们说的是默认配置,即什么也不配置的情况下到底能应对多少并发请求?

        下面我们通过项目演示逐步深入到源码内部,带你去揭开谜底,首先是项目搭建。

一、项目搭建

        项目结构很简单,三个类,一个配置文件,其中一个还是启动类,使用的Spring Boot 版本是 2.2.1.RELEASE。

        添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.4.0</version>
    </dependency>
</dependencies>

        测试接口代码

@Slf4j
@RestController
public class TestController {

    @GetMapping("/testRequest")
    public void test(int num) throws InterruptedException {
        log.info("{} 接受到请求:num={}", Thread.currentThread().getName(), num);
        // 这里让线程睡眠时间长,保证会阻塞住,方便验证
        TimeUnit.HOURS.sleep(1);
    }
}

        下面在来看调试代码,当然你可以使用 Jmeter 等压测工具,这样显的高达上,这里我们使用一个循环不断调用测试接口,具体代码如下:

public class RequestTest {

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            new Thread(() -> {
                HttpUtil.get("http://127.0.0.1:8080/testRequest?num=" + finalI);
            }).start();
        }
        // 阻塞主线程
        Thread.yield();
    }
}

        注意,我们的 application.properties 配置文件中什么内容也没有添加。

二、验证最大并发数

        首先启动项目,让服务跑起来,然后运行 RequestTest.main,这时我们的接口就会收到大量的请求,具体如下:

        接下来,我们直接统计打印的日志,就能统计出处理了多少并发请求。通过下图可以看到,默认配置的情况下,可以接受200个并发请求,在多的请求都被拒绝了,也无法处理。

        于是这时可以得出答案,一个 Spring Boot 项目在默认配置下,最多可以处理200个并发请求,下面来看看这个是怎么来的?

三、源码分析

        首先明确下这 200个 线程是谁的线程,那还用说吗,肯定是 Tomcat 的线程。那说明 Tomcat 最大核心线程数是 200 个,那这个值是如何设置的呢?

        我们知道 Spring Boot 最大的特性是自动装配,那 Spring Boot 启动时内嵌的 Tomcat 参数是如何设置的,下面看下自动装配的具体实现。

        在 spring-boot-autoconfigure 的 spring.factories 文件中有很多自动装配的类,其中一项org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,就是通过 EmbeddedWebServerFactoryCustomizerAutoConfiguration 自动装配类来完成配置的。

        在来具体看下 EmbeddedWebServerFactoryCustomizerAutoConfiguration 的实现。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

    /**
     * Nested configuration if Tomcat is being used.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
    public static class TomcatWebServerFactoryCustomizerConfiguration {

        @Bean
        public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
                ServerProperties serverProperties) {
            return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
        }

    }


    // 其他源码省略


}

        其中将 ServerProperties 传入了 tomcatWebServerFactoryCustomizer 方法中,最后返回了TomcatWebServerFactoryCustomizer,在 ServerProperties 的实现中设置了最大线程数,然后在 TomcatWebServerFactoryCustomizer 中进行了设置,具体如下图标识。

        到这里我们就回答了为什么在默认情况下 Spring Boot 能处理的最大并发数是 200 了,也就是你在配置文件中通过 server.tomcat.max-threads 配置项可以修改最大并发数的原因所在。

        到这里就结束了吗?no,我们继续来看下其他核心配置,来进一步掌握更多的细节。

        3.1 Tomcat 核心配置讲解

        通过 ServerProperties 我们发现 Tomcat 还有很多其他的配置,我们来分别介绍下,具体的源码细节与刚才一样,自己找就可以,这里放上部分代码:

    /**
     * Tomcat properties.
     */
    public static class Tomcat {

        /**
         * Access log configuration.
         */
        private final Accesslog accesslog = new Accesslog();

        /**
         * Regular expression that matches proxies that are to be trusted.
         */
        private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8
                + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16
                + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16
                + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 127/8
                + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
                + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|" //
                + "0:0:0:0:0:0:0:1|::1";

        /**
         * Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
         */
        private String protocolHeader;

        /**
         * Value of the protocol header indicating whether the incoming request uses SSL.
         */
        private String protocolHeaderHttpsValue = "https";

        /**
         * Name of the HTTP header used to override the original port value.
         */
        private String portHeader = "X-Forwarded-Port";

        /**
         * Name of the HTTP header from which the remote IP is extracted. For instance,
         * `X-FORWARDED-FOR`.
         */
        private String remoteIpHeader;

        /**
         * Name of the HTTP header from which the remote host is extracted.
         */
        private String hostHeader = "X-Forwarded-Host";

        /**
         * Tomcat base directory. If not specified, a temporary directory is used.
         */
        private File basedir;

        /**
         * Delay between the invocation of backgroundProcess methods. If a duration suffix
         * is not specified, seconds will be used.
         */
        @DurationUnit(ChronoUnit.SECONDS)
        private Duration backgroundProcessorDelay = Duration.ofSeconds(10);

        /**
         * Maximum amount of worker threads.
         */
        private int maxThreads = 200;

        /**
         * Minimum amount of worker threads.
         */
        private int minSpareThreads = 10;

        /**
         * Maximum size of the form content in any HTTP post request.
         */
        private DataSize maxHttpFormPostSize = DataSize.ofMegabytes(2);

        /**
         * Maximum amount of request body to swallow.
         */
        private DataSize maxSwallowSize = DataSize.ofMegabytes(2);

        /**
         * Whether requests to the context root should be redirected by appending a / to
         * the path.
         */
        private Boolean redirectContextRoot = true;

        /**
         * Whether HTTP 1.1 and later location headers generated by a call to sendRedirect
         * will use relative or absolute redirects.
         */
        private Boolean useRelativeRedirects;

        /**
         * Character encoding to use to decode the URI.
         */
        private Charset uriEncoding = StandardCharsets.UTF_8;

        /**
         * Maximum number of connections that the server accepts and processes at any
         * given time. Once the limit has been reached, the operating system may still
         * accept connections based on the "acceptCount" property.
         */
        private int maxConnections = 10000;

        /**
         * Maximum queue length for incoming connection requests when all possible request
         * processing threads are in use.
         */
        private int acceptCount = 100;

        /**
         * Maximum number of idle processors that will be retained in the cache and reused
         * with a subsequent request. When set to -1 the cache will be unlimited with a
         * theoretical maximum size equal to the maximum number of connections.
         */
        private int processorCache = 200;

        /**
         * Comma-separated list of additional patterns that match jars to ignore for TLD
         * scanning. The special '?' and '*' characters can be used in the pattern to
         * match one and only one character and zero or more characters respectively.
         */
        private List<String> additionalTldSkipPatterns = new ArrayList<>();

        /**
         * Comma-separated list of additional unencoded characters that should be allowed
         * in URI paths. Only "< > [ \ ] ^ ` { | }" are allowed.
         */
        private List<Character> relaxedPathChars = new ArrayList<>();

        /**
         * Comma-separated list of additional unencoded characters that should be allowed
         * in URI query strings. Only "< > [ \ ] ^ ` { | }" are allowed.
         */
        private List<Character> relaxedQueryChars = new ArrayList<>();

        /**
         * Amount of time the connector will wait, after accepting a connection, for the
         * request URI line to be presented.
         */
        private Duration connectionTimeout;

        /**
         * Static resource configuration.
         */
        private final Resource resource = new Resource();

        /**
         * Modeler MBean Registry configuration.
         */
        private final Mbeanregistry mbeanregistry = new Mbeanregistry();

        // 省略代码

    }
  • server.tomcat.min-spare-threads:设定 Tomcat 在启动时创建的最小空闲线程数,即使没有请求到来,也会维持这么多线程空闲等待。其默认值为 10
  • server.tomcat.accept-count:当所有请求处理线程都忙时,可以排队等待的最大请求数量。超过这个数量的请求将被拒绝。其默认值为 100
  • server.tomcat.max-connections:Tomcat能够同时处理的最大连接数,包括正在处理的请求和等待队列中的请求,其默认值为:10000

        一些关键参数就介绍到这里,大家下来可以再自己的代码中配置修改这些值,然后验证一下。其他参数看注释就可以了,欢迎留言讨论。

往期经典推荐

实战分享:Tomcat打破双亲委派模型,实现Web应用独立与安全隔离的奥秘-CSDN博客

你所不知的Tomcat网络通信的玄机-CSDN博客

Raft领导者选举你真的了解了?-CSDN博客

Kafka消息流转的挑战与对策:消息丢失与重复消费问题_kafka发送消息生产者关闭了-CSDN博客

决胜高并发战场:Redis并发访问控制与实战解析-CSDN博客

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐