SpringBoot项目并发处理大揭秘,你知道它到底能应对多少请求洪峰?
Spring 生态的重要性不用多说,Spring Boot 已经成为 Java 后端开发的"标准",但是一个Spring Boot 项目到底能同时应对多少请求呢?你有没有考虑过这个问题呢?这时你可能回文,处理的业务是什么?服务的配置是什么样的?使用的 WEB 容器是什么等等问题,当然我们说的是默认配置,即什么也不配置的情况下到底能应对多少并发请求?下面我们通过项目演示逐步深入到源码内部,带你去揭开
目录
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博客
更多推荐
所有评论(0)