一个 SpringBoot 项目能处理多少请求
首先,这个问题有坑,因为 spring boot 不处理请求,只是把现有的开源组件打包后进行了版本适配、预定义了一些开源组件的配置通过代码的方式进行自动装配进行简化开发。这是 spring boot 的价值。
使用 spring boot 进行开发相对于之前写配置文件是简单了,但是解决问题麻烦了,对于刚入手的开发人员没接触过很多项目的是友好的,但是在实际开发中遇到的问题是多种多样的,然而解决这些问题需要了解内部的运行原理,这个需要看相应的源码,有时需要对现有的自动装配进行自定义处理。
spring boot 很多组件自带了一定程度上支持了容器化部署,例如不需要自己单独处理 web 容器了。在打包的时候引入对应的 starter 就引入了。
如果我是面试官,我不会问这种问题。因为在实际开发中我们遇到的都是具体的问题,能用一句话讲清楚就尽量不用两句话讲清楚,聚焦问题点。
真正处理 http 请求的是 web 容器,web容器是 servlet 规范的实现,比如 tomcat、undertow、jetty 等。spring boot 项目在main()执行的时候启动 web 容器时会加载 spring ioc 容器执行 bean 的初始化操作。
明确了问题接下来就好说了。
下面以 spring boot 2.7.10,因为下面的部分会关系到源码,如果自己去看的话,可能会有无法对应的问题,减少误会和学习成本。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>如果不指定的话上述依赖默认引入 tomcat。
测试代码如下
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.lang.invoke.MethodHandles;
import java.util.concurrent.TimeUnit;/*** @author Rike* @date 2023/7/21*/
@RestController
public class TestController {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());@GetMapping(value = "test")public void test(int num) throws InterruptedException {logger.info("{}接收到请求,num={}", Thread.currentThread().getName(), num);TimeUnit.HOURS.sleep(1L);}
}/*** @author Rike* @date 2023/7/28*/
public class MainTest {public static void main(String[] args) {for (int i = 0; i < 1500; i++) {int finalNo = i;new Thread(() -> {new RestTemplate().getForObject("http://localhost:8080/test?num="+finalNo, Object.class);}).start();}Thread.yield();}
}统计“接受到请求”关键字在日志中出现的次数,为 200 次。
这个结果怎么来的?
最终请求到了 tomcat,所以需要在 tomcat 层次分析问题。
查看线程 dump 信息

org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run

在 getTask() 中可以看到线程池的核心参数

corePoolSize,核心线程数,值为 10
 maximumPoolSize,最大线程数,值为 200
Tomcat 可以同时间处理 200 个请求,而它的线程池核心线程数只有 10,最大线程数是 200。
 这说明,前面这个测试用例,把队列给塞满了,从而导致 Tomcat 线程池启用了最大线程数。
查看一下队列的长度是多少
其中 workQueue 的实现类是 org.apache.tomcat.util.threads.TaskQueue ,继承了 juc 的 LinkedBlockingQueue。

查看构造器在哪里被调用

通过代码跟踪,得知在 org.apache.catalina.core.StandardThreadExecutor 中 maxQueueSize 线程池的队列最大值,默认为 Integer.MAX_VALUE。
目前已知的是核心线程数,值为 10。这 10 个线程的工作流程是符合预测的。
 但是第 11 个任务过来的时候,本应该进入队列去排队。
 现在看起来,是直接启用最大线程数了。
接下来查看一下 org.apache.tomcat.util.threads.ThreadPoolExecutor 的源码


标号为1的地方,就是判断当前工作线程数是否小于核心线程数,小于则直接调用 addWorker(),创建线程。
 标号为2的地方主要是调用了 offer(),看看队列里面是否还能继续添加任务。
 如果不能继续添加,说明队列满了,则来到标号为3的地方,看看是否能执行 addWorker(),创建非核心线程,即启用最大线程数。
主要就是去看 workQueue.offer(command) 这个逻辑。
如果返回 true 则表示加入到队列,返回 false 则表示启用最大线程数。
 这个 workQueue 是 TaskQueue。
看一下org.apache.Tomcat.util.threads.TaskQueue#offer

标号为1的地方,判断了 parent 是否为 null,如果是则直接调用父类的 offer 方法。说明要启用这个逻辑,我们的 parent 不能为 null。

在 org.apache.catalina.core.StandardThreadExecutor 中进行了 parent 的设置,当前 ThreadPoolExecutor 为 org.apache.tomcat.util.threads.ThreadPoolExecutor。即 parent 是 tomcat 的线程池。
标号2表明当前线程池的线程数已经是配置的最大线程数了,那就调用 offer 方法,把当前请求放到到队列里面去。
 标号为3的地方,是判断已经提交到线程池里面待执行或者正在执行的任务个数,是否比当前线程池的线程数还少。
 如果是,则说明当前线程池有空闲线程可以执行任务,则把任务放到队列里面去,就会被空闲线程给取走执行。
然后,关键的来了,标号为4的地方。
如果当前线程池的线程数比线程池配置的最大线程数还少,则返回 false。
如果 offer() 返回 false,会出现什么情况?

是不是直接开始到上图中标号为3的地方,去尝试添加非核心线程了?
 也就是启用最大线程数这个配置了。
这里可以得知,java自带的线程池和tomcat线程池使用机制不一样
JDK 的线程池,是先使用核心线程数配置,接着使用队列长度,最后再使用最大线程配置。
Tomcat 的线程池,就是先使用核心线程数配置,再使用最大线程配置,最后才使用队列长度。
面试官的原问题就是:一个 SpringBoot 项目能同时处理多少请求?
一个未进行任何特殊配置,全部采用默认设置的 SpringBoot 项目,这个项目同一时刻最多能同时处理多少请求,取决于我们使用的 web 容器,而 SpringBoot 默认使用的是 Tomcat。
Tomcat 的默认核心线程数是 10,最大线程数 200,队列长度是无限长。但是由于其运行机制和 JDK 线程池不一样,在核心线程数满了之后,会直接启用最大线程数。所以,在默认的配置下,同一时刻,可以处理 200 个请求。
在实际使用过程中,应该基于服务实际情况和服务器配置等相关消息,对该参数进行评估设置。
那么其他什么都不动,如果我仅仅加入 server.tomcat.max-connections=10 这个配置呢,那么这个时候最多能处理多少个请求?
重新提交 1000 个任务过来,在控制台输出的确实是 10 个。
那么 max-connections 这个参数它怎么也能控制请求个数呢?
 为什么在前面的分析过程中我们并没有注意到这个参数呢?

因为 spring boot 设置的默认值是 8192,比最大线程数 200 大,这个参数并没有限制到我们,所以我们没有关注到它。
 当我们把它调整为 10 的时候,小于最大线程数 200,它就开始变成限制项了。
还有这样的一个参数,默认是 100
server.tomcat.accept-count=100
server.tomcat.max-connections
最大连接数,达到最大值时,操作系统仍然接收属性acceptCount指定的连接
 server.tomcat.accept-count
 
所有请求线程在使用时,连接请求队列最大长度
实践验证一下
| max-connections 指定为1000 | max 指定为1000 | |
| max-connections 取默认值(8192) | 无 | 正常,但是只处理了200个请求 | 
| max取默认值 (200) | 正常接收,请求端报连接拒绝异常,获取所有请求中的1000个进行处理 | 无 | 
server.tomcat.max-connections 与 server.tomcat.threads.max 的关系
当 server.tomcat.max-connections > server.tomcat.threads.max,只会处理 server.tomcat.threads.max 大小的请求,其他的会被拒绝。
 打印的日志线程的id是指 server.tomcat.threads.max 里的。
server.tomcat.max-connections 类似一个大门,决定了同一时刻有多少请求能被处理,但是最终处理的不是它,而是 server.tomcat.threads.max 控制。
可以理解为大门和小门的关系。
参数 server.tomcat.threads.max 经过调整后(大于默认值),发现只有对应的核心线程数量对应的请求,由此考虑到进了队列的数据未处理。
tomcat 相关配置如下
org.apache.tomcat.util.threads.ThreadPoolExecutor
 tomcat的线程池在juc的ThreadPoolExecutor基础上进行了处理命名为自己的线程池,
对应的核心线程数、最大线程数、阻塞队列大小
org.apache.tomcat.util.net.AbstractEndpoint 中
 minSpareThreads 核心线程数,默认值为 10
 maxThreads 最大线程数,默认值为 200
 maxConnections 最大连接数,默认值为 8192
 acceptCount
 允许服务器开发人员指定 acceptCount(backlog)应该用于服务器套接字。默认情况下,此值是100。
org.apache.catalina.core.StandardThreadExecutor 中
 maxQueueSize 线程池的队列最大值,默认为 Integer.MAX_VALUE
 org.apache.tomcat.util.threads.TaskQueue 继承了 juc 的 LinkedBlockingQueue
spring boot 把这些默认配置参数在自定义配置中进行了相应设置,最终还是通过自动装配访问对应的 web 容器来处理对应的请求。
org.springframework.boot.autoconfigure.web.ServerProperties
设置了 web 容器相关的配置参数。
 org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
 
对于各种 web 容器进行适配处理。
/** Copyright 2012-2023 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.boot.autoconfigure.web.embedded;import io.undertow.Undertow;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.UpgradeProtocol;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.xnio.SslClientAuthMode;
import reactor.netty.http.server.HttpServer;import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;/*** {@link EnableAutoConfiguration Auto-configuration} for embedded servlet and reactive* web servers customizations.** @author Phillip Webb* @since 2.0.0*/
@AutoConfiguration
@ConditionalOnNotWarDeployment
@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 {@Beanpublic TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,ServerProperties serverProperties) {return new TomcatWebServerFactoryCustomizer(environment, serverProperties);}}/*** Nested configuration if Jetty is being used.*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })public static class JettyWebServerFactoryCustomizerConfiguration {@Beanpublic JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,ServerProperties serverProperties) {return new JettyWebServerFactoryCustomizer(environment, serverProperties);}}/*** Nested configuration if Undertow is being used.*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })public static class UndertowWebServerFactoryCustomizerConfiguration {@Beanpublic UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,ServerProperties serverProperties) {return new UndertowWebServerFactoryCustomizer(environment, serverProperties);}}/*** Nested configuration if Netty is being used.*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass(HttpServer.class)public static class NettyWebServerFactoryCustomizerConfiguration {@Beanpublic NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment,ServerProperties serverProperties) {return new NettyWebServerFactoryCustomizer(environment, serverProperties);}}}
参考链接
https://mp.weixin.qq.com/s/OTs2KAZ6DSbzH_WC0AWCSw
相关文章:
 
一个 SpringBoot 项目能处理多少请求
首先,这个问题有坑,因为 spring boot 不处理请求,只是把现有的开源组件打包后进行了版本适配、预定义了一些开源组件的配置通过代码的方式进行自动装配进行简化开发。这是 spring boot 的价值。 使用 spring boot 进行开发相对于之前写配置文…...
Python中的r字符串前缀及其用法详解
Python中的r字符串前缀及其用法详解 1. 介绍 1.1 什么是r字符串前缀 在Python中,r字符串前缀是一种特殊的字符串前缀,用于表示原始字符串。当一个字符串以r前缀开始时,它将被视为原始字符串,其中的转义字符将被忽略。 1.2 r字…...
 
LabVIEW实现三相异步电机磁通模型
LabVIEW实现三相异步电机磁通模型 三相异步电动机由于经济和出色的机电坚固性而广泛用于工业化应用。这台机器的设计和驱动非常简单,但在控制扭矩和速度方面,它隐藏了相当大的功能复杂性。通过数学建模,可以理解机器动力学。 基于微分方程的…...
读书会-《影响力》
《影响力》这本书的作者罗伯特西奥迪尼时全球知名说服力研究权威。因其在影响力研究领域的开创性,人们将其称为“影响力研究领域的本杰明富兰克林”。这本书从人们的心理状态,进行了很多实验研究,总结出了7大规律。如果从事营销,需…...
 
141. 环形链表
简单 1.9K 相关企业 给你一个链表的头节点 head ,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链…...
 
学习笔记|大模型优质Prompt开发与应用课(二)|第一节:大模型应用密码—Prompt的一千种打开方式
文章目录 第一节:大模型应用密码—Prompt的一千种打开方式01你可能听过一个小故事1910华盛顿纺织厂罢工事件 02 小问题:哪些场景会被提效类目一︰减少重复性工作的成本(降本)例如∶做策划初稿、写JD、润色文案prompt生成结果prompt生成结果prompt生成结果promptprom…...
QT chart进行画图
说明 QT Chart 是一个用于在 Qt 应用程序中绘制图表的开源库。它提供了多种类型的图表,如线图、柱状图、饼图等,可以用于可视化数据和统计信息。QT Chart 是一个基于 Qt 绘图框架的扩展,可以轻松集成到现有的 Qt 应用程序中。 使用 QT Chart,你可以通过简单的代码来创建和…...
 
Web3将自己写在合约中的代币添加到MetaMask中管理
上文 Web3带着大家根据ERC-20文档编写自己的第一个代币solidity智能合约 带着大家在智能合约中创建了一个自己的代币系统 我们可以在MetaMask中去导入 ganache环境下模拟出来的第一和第二个账号 我们这里 可以看到他们的 ETH 但看不到自己的代币符号 没关系 我们点击这下面的…...
【微信小程序】显示自带的弹窗,包括加载中,成功,错误,提示,警告
在微信小程序中,可以使用以下方法来显示自带的弹窗: 显示加载中的弹窗: wx.showLoading({title: 加载中,mask: true });显示成功的弹窗: wx.showToast({title: 成功,icon: success,duration: 2000 });显示错误的弹窗࿱…...
vue-element-plus-admin框架的tag上下文切换bug
问题 首先贴上该框架的链接:https://github.com/kailong321200875/vue-element-plus-admin 在对路由进行部分修改后,网站多次切换tag时,控制台会出现报错:Cannot read properties of undefined (reading offsetLeft)。 我在框架…...
vue中,父子组件传递参数 props 实现方式
通过 Prop 向子组件传递数据 001:父组件》子组件通信 <template><div><h1>这里是父元素</h1>//******<childComponent :detailMes"detailMes"/></div> </template><script>import childComponent from…...
Unity如何快速接入iOS和GooglePlay的成就排行榜等GameCenter功能
一般在游戏开发中,经常有成就排行榜的需求,按照我们的理解,通常是要自己导入谷歌的sdk,或者苹果的sdk,然后封装后通过桥接来调用。 不用这么复杂,本鱼蛋(egostudio 防爬)告诉大家一个方法,其实…...
 
Unity下如何实现低延迟的全景RTMP|RTSP流渲染
技术背景 Unity3D可以用于创建各种类型的的应用程序,包括虚拟现实、培训模拟器等。以下是一些可以使用Unity3D全景播放的场景: 虚拟现实体验:全景视频可以用来创建逼真的虚拟环境,使用户能够感受到身临其境的感觉;培…...
 
STM32 USB使用记录:HID类设备(后篇)
文章目录 目的基础说明项目构建与代码调整接收发送代码与测试示例链接报告描述符总结 目的 接上篇: 《STM32 USB使用记录:HID类设备(前篇)》 USB HID 类的设备有个比较大的好处是大部分时候接入主机中都是可以免驱使用的。这篇文…...
 
C# 快速写入日志 不卡线程 生产者 消费者模式
有这样一种场景需求,就是某个方法,对耗时要求很高,但是又要记录日志到数据库便于分析,由于访问数据库基本都要几十毫秒,可在方法里写入BlockingCollection,由另外的线程写入数据库。 可以看到,在…...
Pandas将对角线元素设为1
Pandas将对角线元素设为1 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 入门之pandas的使用 提示:np.fill_diagonal(df.values, 1)的用法 Pandas将对角线元素设为1 Pandas将对角线元素设为…...
WPF实战学习笔记28-登录界面
添加登录界面UI 添加文件loginview.xaml。注意本界面使用的是md内的图标。没有登录界面的图片 <UserControlx:Class"Mytodo.Views.LoginView"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsof…...
01背包
动态规划解题步骤: 动态规划问题,一般从三个步骤进行考虑。 步骤一:集合及集合的状态。 所谓的集合,就是一些方案的集合。 用 g[i][j] 表示从前 i 种物品中进行选择,且总体积不大于 j 的各个选法获得的价值的集合。注意&#…...
 
064、故障处理之OMM_TiDB
oom 内存溢出,内存泄漏,相当于TiDB不能用了 TiDB Server OOM对业务的影响 TiDB Server上的业务SQL会失败业务响应时间升高前端体验变差 诊断方法 客户端应用 ERROR 2013(HY000): Lost connection to MySQL Server during query日志 dmesg -T | gr…...
 
网络设备中的配置文件管理
建立强大网络的第一步是为灾难和网络中断做好准备,许多企业在中断期间遭受损失,因为他们缺乏备份计划并且配置管理不达标,使用配置文件管理工具进行适当的配置文件管理不仅有助于处理网络中断,还有助于优化网络性能。 使用配置文…...
 
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
 
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
 
【网络安全】开源系统getshell漏洞挖掘
审计过程: 在入口文件admin/index.php中: 用户可以通过m,c,a等参数控制加载的文件和方法,在app/system/entrance.php中存在重点代码: 当M_TYPE system并且M_MODULE include时,会设置常量PATH_OWN_FILE为PATH_APP.M_T…...
 
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
 
系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...
 
《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
