Springboot可以同时处理多少个请求呢

最近耽误了些时间写博文,实际上是没时间来更新。工作和生活的平衡杆还没有熟悉,所以处理得还不是很好。不过还好今天有时间了。

言归正传,这两天看到了一个博文的推送,标题是Springboot可以同时处理多少个请求?这不禁引起了我的好奇,因为我也从没有认真关注过这个问题,所以我认真研读了一下,将读后感以及自己的一些实践记录下来。

springBoot Web的启动过程

首先,我们在讲springboot可以同时并发多少个请求时,我们先来了解springboot的启动过程,我们都知道springboot有内嵌tomcat,所以我们启动的时候只需要java -jar就可以,那具体的过程我们一起来学习下。

java -jar的启动方式,我们就离开一个文件,那就是描述文件MMANIFEST.MF。以下是我到start.spring.io去生成的一个springboot web工程,工程很简单,只有一个spring-boot-starter-web依赖,然后我通过mvn clean package打包之后,用反编译工具去查看它的jar包。

image-20230420203442536

我们可以看到描述文件里有两个关键的配置Main-ClassStart-Claass,对于Main-class我们可能会比较熟悉,因为我们在打包普通的java工程的时候,需要指定这个入口类。那接下来我们就跟着这个入口类来看看当我们用java -jar去启动的时候,springboot这个工程它到底发生了什么事儿~

JarLauncher.class

我们到了主入口类,

image-20230420210532044

main方法直接调用了爷爷类的launch方法

image-20230420211814949

launch方法中干了两件事,第一件就是获取了类加载器(classLoader),另一件就是通过getMainClass()方法获取了springboot的启动类,我们把目光放到this.getMainClass(),然后我们就在爷爷类里找这个方法
image-20230420212016253

很明显,这是一个抽象类,那我们就去JarLauncher的父类ExecutableArchiveLauncher里找,果然被我们找到了它的具体实现:

image-20230420212145090

我们可以看到方法里很简单的逻辑,首先呢获取到描述文件MANIFEST.MF,然后从描述文件中找到Start-Class,这个是不是很熟悉呢,没错,我们往上翻可以看到这个就是我们springboot的启动类。

ok,到了这里我们已经知道了Start-ClassMain-Class的关联关系了,那我们回去爷爷类的launch方法,最后一行我们可以知道它调用了一个重载后的launch方法,我们进入到这个方法里去一探究竟!

image-20230420212734079

我们可以看到这个方法中去实例化了一个MainMethodRunner对象,然后执行了它的run方法,我们来看一下这个MainMethodRunner是个什么东西?

image-20230420212906781

我们可以从上面的源码看到run方法,是不是很熟悉呢?是的,这里用到了反射去执行了springboot中的main方法。

到这里,我们算是搞清楚了从JarLauncher到启动类的一整个过程,然而我们都知道web项目是运行在web容器当中的,而springboot内置了tomcat,那么启动类的main方法又是如何启动tomcat呢?我们花点时间继续来拜读下main方法的源码。

image-20230420213512454

话不多说,我们直入run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 此处定义了spring容器
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 创建spring容器AnnotationConfigServletWebServerApplicationContext
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 初始化容器
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}

我们大概过一下上面的代码流程,就是创建了个spring容器,然后初始化,那我们到初始化再瞧一瞧

image-20230420214128410

该方法又调用了refresh方法,一路refresh下去,最终调用了AbstractApplicationContextrefresh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context. 初始化bean(因为这不是我们本文重点,我们就不展开说了)
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses. Spring Boot实现了该方法创建了web容器,这是我们想要看的东西!
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}

我们可以看到onRefresh的多种实现

image-20230420214618948

很明显,我们直接到ServletWebServerApplicationContextonRefresh方法中:

image-20230420214732956

一眼我们就看到我们苦苦寻找的东西!createWebServer()

image-20230420220110653以上代码通过ServletWebServerFactory 工厂类的getWebServer方法创建了web容器。

image-20230420220223411

从上面的实现,我们可以看到实际上springboot内置了JettyTomcatUndertow三种web容器,因为我们主要是讲的Tomcat,所以我们就进到TomcatServletWebServerFactory这个工厂类去揭开创建tomcat容器的面纱

image-20230420220924051

我们可以大概看到整个tomcat的构造过程,那最后的getTomcatWebServer(tomcat),又将整个构建好的tomcat进行了什么操作呢?

image-20230420221207295

一不做二不休,我们再进入到TomcatWebServer的这个构造方法中

image-20230420221356900

看来,我们来到了最后的这个tomcat的初始化了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();

Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});

// Start the server to trigger initialization listeners 启动tomcat!
this.tomcat.start();

// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();

try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}

// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}

到这里,我们已经知道了springboot通过tomcat启动项目的流程了!

那回到我们的文章题目Springboot可以同时处理多少个请求呢?,实际上,它问的并不是springboot本身,而是承载这个web服务的tomcat容器!

Tomcat的关键配置

本文以tomcat容器启动来提供一个分析这个题目的思路,解释但不局限!

每一次HTTP请求到达Web服务,tomcat都会创建一个线程来处理该请求,那么最大线程数决定了Web服务容器可以同时处理多少个请求。

我们回到工程本身,打开配置文件去检索,我们可以看到springboot默认内置tomcat的最大线程数是200

image-20230420222240001

其次,当调用HTTP请求数达到tomcat的最大线程数时,还有新的HTTP请求到来,这时tomcat会将该请求放在等待队列中,这个acceptCount就是指能够接受的最大等待数,默认是100,如果等待队列也被放满了,这个时候再来新的请求就会被tomcat拒绝(connection refused)。

image-20230420222541848

最后,我们来看一个参数,就是最大连接数(在同一时间,tomcat能够接受的最大连接数),这个值默认是8192。如果设置为-1,则禁用maxconnections功能,表示不限制tomcat容器的连接数。 maxConnections和accept-count的关系为:当连接数达到最大值maxConnections后,系统会继续接收连接,但不会超过acceptCount的值。

image-20230420222752353

这大概我们需要关注这个问题的时候,需要第一时间想到的几个关键配置。

而对于默认值,我们是不是可以改大点来提升并发量,答案毋庸置疑是可以的,但是这个操作是需要根据硬件条件和业务需求来进行增加的,同时需要考虑的是增加线程是有成本的,更多的线程,不仅仅会带来更多的线程上下文切换成本,而且意味着带来更多的内存消耗。JVM中默认情况下在创建新线程时会分配大小为1M的线程栈,所以,更多的线程意味着需要更多的内存。

综合考虑,才能将性能提高到极限!


本篇文章就讲到这里,希望对你们能有所启发,谢谢~


Springboot可以同时处理多少个请求呢
https://gcoder5.com/2023/04/20/Springboot可以同时处理多少个请求呢/
作者
Gcoder
发布于
2023年4月20日
许可协议