2026/10/8 20:43:36

SpringBoot能处理多少请求?从线程池到连接池的性能链路

SpringBoot能处理多少请求?从线程池到连接池的性能链路 有个面试题我印象很深“一个 SpringBoot 项目能处理多少请求”乍一听像是让背数字其实问的是一个完整的性能世界观。我前前后后拿它问过不下三十个候选人能答到点子上的不超过三个。这题真正考的是你会不会从连接器、线程池、业务耗时、下游资源几个维度去评估一个接口的真实承载能力而不是等面试官抛出标准答案。这篇内容我把从默认参数拆到压测实操、再到面试应答的完整链路都梳理一遍适合正在准备 Java 后端面试的人也适合被线上接口突然变慢、连接拒绝折磨到睡不着觉的人。看完至少能回答出一个层次分明、能落地的版本。1. 从数字说起SpringBoot 默认能扛多少请求1.1 默认参数决定的“接纳上限”先说结论SpringBoot 内嵌 Tomcat 时默认的“工作线程数”是 200等待队列长度 accept-count 是 100。也就是说在没有自定义任何配置的情况下大约能同时接纳 300 个请求在容器内部流转200 个正在被工作线程处理100 个在队列里排队。一旦超过这个量级Tomcat 的拒绝策略就开始生效客户端会看到连接重置、请求超时或者直接收到 503。这组数字很多人记不住或者记混了。我有个土办法把 Tomcat 想象成一家银行max-threads 是柜台窗口数默认 200 个窗口accept-count 是大厅椅子数默认 100 张max-connections 是银行大门的吞吐上限默认 8192。窗口满了、椅子也坐满了后面再来客户就只能被保安挡在外面。注意max-connections 默认 8192 只有在极端情况下才会成为瓶颈比如大量慢客户端挂着 Keep-Alive 不释放连接把门口堵死了否则日常压测里 8192 远用不完。还有一个容易忽略的细节SpringBoot 的默认核心线程数 min-spare-threads 是 10也就是说启动初期只会预创建少量线程流量上来后线程池才会逐步扩展到 200。很多新手看监控发现线程数才十几个就以为 Tomcat 配置错了实际上只是还没到扩容条件。1.2 并发数和 QPS 是两码事搞清楚默认参数之后下一个大坑就是很多人把“能处理多少请求”直接理解成“每秒能处理多少请求”。实际上这是两个完全不同的指标并发数指的是同一时刻正在处理的请求数量QPS 是每秒完成的请求吞吐。两者之间隔着一条关键公式并发数约等于 QPS 乘以单请求平均耗时。这就是 Little 定律的工程版本。举个例子一个接口平均响应时间 50 毫秒你希望支撑 2000 QPS那么你需要的同时处理能力大约是多少2000 乘以 0.05 等于 100 个并发线程。换句话说200 个 Tomcat 工作线程理论上能撑起 4000 QPS但前提是每个接口必须在 50 毫秒内跑完而且线程全部处于工作状态。如果接口平均耗时是 500 毫秒200 个线程撑死只能做到 400 QPS。这就是为什么面试官问“能处理多少请求”时只回答一个数字永远不完整同样的线程池不同的业务耗时吞吐天差地别。所以我在回答问题时的习惯是先分清楚对方问的是“Tomcat 能接纳多少并发连接”还是“业务接口能扛多少 QPS”。前者有明确的默认参数后者必须结合接口逻辑和下游依赖才能估算。这个区分本身就是面试官想看到的思路。2. 请求处理链路与线程模型为什么不能只看 Tomcat2.1 一个请求从端口走进 Controller要理解 ThreadPool 默认值先得把请求的一生画出来。客户端发起 TCP 连接Tomcat 的 Acceptor 线程负责接收连接接着由 Poller 线程负责监听 socket 上的数据是否可读读到的 HTTP 请求会被包装成一个任务丢给工作线程池。工作线程从线程池里取任务拿着这个请求一路走进 DispatcherServlet、HandlerMapping、Controller、Service、DAO直到响应写完、连接归还线程才算解脱。这条链路里最核心的事实是SpringBoot 默认的 Servlet 模型是同步阻塞模型一个请求占用一个工作线程Controller 里的所有耗时——查数据库、调第三方接口、算复杂逻辑——都算作线程占用时间。只要 Controller 没有返回线程就被死死占住别的请求就只能等。这也是为什么我说不能只看 Tomcat 配置你必须知道每个请求平均占住工作线程多久。我经常拿它对比响应式模型。在 WebFlux 里一个线程可以同时服务很多请求因为它是事件驱动、非阻塞的请求在等待 IO 时线程会被释放出来做别的事。两种模型没有谁绝对更好但如果你在用默认的 SpringBoot MVC就必须接受“一个请求一个线程”这个设定然后在这个设定下做优化。2.2 线程数不是越大越好很多人听完“默认 200 线程”就觉得小第一反应是把 max-threads 调到 500、1000以为线程多了就能处理更多请求。这个思路在并发量不大的时候确实管用但线程堆到一定程度后收益会急剧下降甚至变成负数。线程切换需要 CPU 时间每个线程都有独立的栈内存线程多了 GC 和锁竞争也会恶化到时候接口吞吐反而下降。我踩过一次很深的坑给一个网关服务调 max-threads 到 400压测吞吐还不如原来的 200。原因是大量线程同时卡在下游调用的超时等待上一个超时重试就把线程池和连接池全部搞乱CPU 全花在线程切换上。后来把线程数改回 200同时给下游加上合理的超时和熔断吞吐反而翻了一倍。那线程数到底怎么给教科书上说 CPU 密集型任务设置为核心数加一IO 密集型任务可以多配一些比如核心数乘以二再按阻塞比例修正。Tomcat 默认 200 对大多数互联网业务来说其实已经是“偏大”的下限不要轻易动它。真要优化优先看业务耗时和下游连接池而不是无脑堆线程。这也是面试时很讨喜的一个观点默认值未必合理但盲目调参一定不合理。3. 真正的瓶颈在下游数据库连接池与阻塞调用3.1 一个连接池把吞吐打回原形Tomcat 线程池 200看着挺宽裕但 SpringBoot 默认的 HikariCP 数据库连接池 maximum-pool-size 只有 10。这意味着不管你有多少工作线程同一时刻能真正执行数据库查询的操作只有 10 个。假设一个接口里有一次查询耗时 50 毫秒那 10 个数据库连接每秒能完成的请求量大约是 10 除以 0.05也就是 200 个。Tomcat 这边空有 200 个线程其中 190 个都堵在等连接上。这个差距是很多人线上事故的根源。你压测一个简单接口发现 Tomcat 线程池没满于是自信满满上了线结果大促时数据库连接池被打满请求全线超时。监控一看Tomcat 线程全在 WAITING栈里齐刷刷写着 HikariPool.getConnection。问题根本不在 Tomcat而在于连接池只有 10 个。配置层面可以这样放大数据库连接池spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000注意连接池也不是越大越好。数据库服务端对连接数有上限连接太多同样会拖垮数据库。我一般用这个背推法目标 QPS 乘以单次查询占用连接的时间就得到需要的连接数。比如目标 2000 QPS每次查询 50 毫秒就需要 2000 乘以 0.05 等于 100 个连接。如果这个数远超数据库实例的能力就得考虑加缓存、批量处理或者读写分离而不是继续加连接数。3.2 同步阻塞模型下的理论计算把整个链路串起来算一遍你会发现很有意思。假设接口平均耗时 50 毫秒且不依赖数据库连接池200 个 Tomcat 线程的理论 QPS 是 4000。一旦引入数据库连接池10 连接瓶颈立刻落到连接池上理论 QPS 掉到 200直接缩水二十倍。要是再调一次第三方 HTTP 接口平均等待 200 毫秒又没有独立的连接池隔离那就算 Tomcat 有 200 线程实际吞吐也就在 1000 左右而且这 1000 个并发全部卡在外部等待上。我面试时特别喜欢让候选人现场算这道题因为算法本身不复杂重点看你会不会把“并发数、耗时、连接池、吞吐”这几个量串起来。能算出“200 线程加 10 连接池理论只剩 200 QPS”的人说明他真的理解系统瓶颈在哪而不是背过几个默认配置。那优化方向也就清晰了要么降低单请求耗时比如加缓存、批量查询要么增加关键连接池数量比如数据库连接池、Redis 连接池要么把同步阻塞改成异步化让线程在等待 IO 时被释放。具体选哪条取决于压测数据告诉你瓶颈在哪。脱离数据做的性能优化基本都是瞎猜。4. 压测实操拿数据说话4.1 压测工具与基本流程纸上算完接下来就是实战。我平时最常用的压测工具是 wrk轻量、单机就能压出很高的并发适合快速验证接口承载能力。安装也简单macOS 上用 Homebrew 一行搞定Linux 下编译安装也不算麻烦。如果需要复杂场景——登录态、多接口混合、按比例请求我会换成 JMeter 或 Gatling但日常摸底用 wrk 足够。基本流程是这样先起一个 SpringBoot 应用准备一个目标接口比如返回固定 JSON 的/api/ping然后跑一条很简单的命令wrk -t8 -c200 -d30s http://localhost:8080/api/ping参数含义很直观8 个压测线程同时保持 200 个连接持续压 30 秒。跑完会输出 QPS、平均延迟、延迟分布等指标。核心要看 p99也就是最慢的 1% 请求延迟这比平均值诚实得多。平均值会被大量快请求拉低p99 才是真实用户体验的参考线。压测期间我一般同时开着两个监控一个看 CPU 和内存用 top 或者容器平台的监控面板另一个看 SpringBoot 的 Actuator 指标重点盯http.server.requests和 Tomcat 线程数。如果 CPU 没满但 QPS 上不去大概率就是线程阻塞在下游如果 CPU 满了那就得看是 GC 问题还是代码本身计算密集。4.2 实测数据如何反推配置做一次最简单的实验你就能感受到耗时的威力。一个只返回固定字符串的接口本机压测QPS 做到几千甚至上万很正常给接口里塞一行Thread.sleep(50)模拟一次普通的数据库查询耗时200 并发下 QPS 会掉到 3000 左右跟理论值 4000 有差距但量级对得上。再把 sleep 改成 100 毫秒QPS 又掉一半接近 1500。这说明在这个场景下Tomcat 线程数不再是瓶颈单请求耗时直接决定吞吐上限。根据压测数据反推配置时用我之前提到的公式所需并发线程数 目标 QPS × 单请求平均耗时。比如业务要求 1000 QPS平均耗时 100 毫秒那就需要大约 100 个线程。默认 200 够用你甚至可以考虑调小一点减少线程切换开销。反过来说如果算出来需要 2000 个线程那第一反应不该是调 max-threads而是怀疑单请求耗时太长先做性能剖析把耗时降下来。我习惯在压测报告里记录一组参数QPS、平均延迟、p99、Tomcat 线程活跃数、数据库连接池活跃数、CPU 使用率。压完一轮看一眼哪个指标先接近上限瓶颈基本就浮出水面了。没有压测数据的性能讨论在面试里也显得特别虚能当场说出“我上次压测看到一个现象”可信度会高很多。5. 线上故障排查实战记录5.1 两个典型的崩溃现场第一个事故现场是连接频繁被拒。现象是客户端偶发报错错误信息是 Connection refused 或者超时。刚开始以为是网络抖动后来把日志拉出来看发现 Tomcat 线程池打满accept-count 队列也满了新请求直接被拒绝。那次就是因为一个促销接口的数据库查询太慢平均耗时从 30 毫秒涨到 300 毫秒导致 Tomcat 线程被长期占住。排查方法是先看线程栈命令是jstack 进程号。如果你看到大量线程停在类似java.lang.Thread.State: WAITING (parking)的位置下面跟着HikariPool.getConnection基本就是数据库连接池耗尽如果线程栈停在业务代码或 HTTP 调用的 socketRead 上那就是下游接口变慢。一旦定位到是哪个环节解决办法就明确了优化慢查询、给下游加超时、扩大连接池三选一或者组合拳。第二个事故现场更有迷惑性服务器 CPU 很低Tomcat 线程也不忙但接口就是感觉卡顿。那次折腾了很久才发现是有大量移动端的长连接占满了 max-connectionsKeep-Alive 挂着一堆半死不活的连接新的请求连接进不来。Tomcat 的 max-connections 默认 8192按说很大但那段时间用户量涨了一波长连接数逼近了上限。处理方法是调大连接上限、缩短 Keep-Alive 超时同时让接入层做连接管理。这个坑特别隐蔽不懂连接器那一层的人很难想到。5.2 监控指标与快速定位表格排查多了我整理了一张速查表遇到症状直接对照定位效率高很多。症状可能原因快速定位手段常用解法请求超时、连接拒绝Tomcat 线程池满jstack、Actuator Tomcat 指标降低业务耗时必要时调 max-threads、accept-count大量线程 WAITING数据库连接池耗尽jstack 搜索 HikariPool调大连接池、优化 SQL、加缓存CPU 低但响应慢线程阻塞在下游 IOarthas trace、链路追踪异步化、加超时熔断CPU 高但 QPS 低代码计算密集或 GC 频繁火焰图、GC 日志优化代码、调整堆参数大量 TIME_WAIT短连接过多ss -s 观察连接状态开启 Keep-Alive 连接复用连接数逼近上限慢客户端挂长连接netstat 统计 ESTABLISHED调整 max-connections、Keep-Alive 超时这张表不是理论推导是几次线上事故的沉淀。特别是“CPU 低但响应慢”这一行很多人会下意识以为是机器性能差其实绝大多数是下游等待导致线程空转。你用arthas trace看一眼方法调用耗时往往能直接看到究竟是哪个远程调用拖了后腿。排查性能问题最忌讳靠感觉猜一定要先用工具拿到证据再动手。6. 面试答案模板与扩展加分项6.1 一套直接能用的应答框架如果面试官真的问出“一个 SpringBoot 项目能处理多少请求”我会这样分三层回答。第一层说明默认配置内嵌 Tomcat 默认工作线程 200等待队列 100连接上限 8192所以容器的接纳能力大概在 300 左右同时在途请求超出这个量会出现排队或拒绝。第二层指出真正的瓶颈实际 QPS 取决于单请求平均耗时用公式算的话200 个线程、50 毫秒耗时理论上限约 4000 QPS但数据库连接池默认只有 10 个一旦接口依赖数据库吞吐会被连接池卡到 200 QPS 这个量级。第三层强调优化方向先压测找到瓶颈再决定是降低耗时、扩大连接池还是改异步模型。这个回答好在哪它不是背数字而是展示了一个完整的分析链路。面试官通常不会只满足于“200 线程”这个答案他更想听到你从 Tomcat 线程池一路分析到数据库连接池、再到业务耗时的全过程。如果你还能补一句“所以最好以压测数据为准”那基本上就把这道题答扎实了。我还会提一句一个 SpringBoot 项目能处理多少请求取决于系统中最弱的那一环。Tomcat 线程池只是入口数据库连接池、下游接口、缓存带宽甚至序列化框架任何一环都可能成为新的天花板。这句话听起来简单但能把“系统性能是短板决定”这个意识体现出来面试观感会完全不一样。6.2 想拿高分就聊这些方向答完核心问题如果气氛允许我会主动延展两个方向。第一个是异步化。Servlet 3.1 之后支持异步处理Spring MVC 提供了 DeferredResult、CompletableFuture 配合Async核心思路是先释放 Tomcat 工作线程等下游结果回来了再写响应。这样一来Tomcat 线程数不再是硬瓶颈200 个线程能撑住更多的在途请求。第二个是响应式栈Spring WebFlux 默认使用 Reactor Netty工作线程与 CPU 核数相关往往只有十几个但通过非阻塞 IO 可以支撑远高于线程数的并发。它门槛高调试复杂但确实是处理 IO 密集型高并发场景的一个重要方向。还有一点我要特别提出来线上实际接入流量时前面通常还有一层 Nginx 或网关。Nginx 的 worker 数量和每个 worker 能维持的连接数也会决定整个链路对外呈现的并发能力。你把后端 Tomcat 调得再大如果接入层连接数先到了上限一样会出现连接拒绝。所以讨论“一个 SpringBoot 项目能处理多少请求”时最好把接入层、应用层、数据层串成一条完整链路来分析这才是生产环境的真实视角。最后再补充一个面试时经常被追问的细节SpringBoot 的自动配置源码里Tomcat 的配置项都在ServerProperties里数据库连接池在DataSourceAutoConfiguration里调优。能说出这一层说明你不是只用过配置还知道配置背后的机制。这属于加分项答不上来也不影响主干但答上来了面试官会高看你一眼。我个人在实际操作中的体会是这种题和“自动装配原理”一样考的是有没有建立完整的系统性思维。能背出默认参数的候选人已经超过一半了能把线程池、队列、连接池、响应时间串成一条链路讲清楚的我基本都会给高分。如果你正在准备面试拿这道题练练手逼自己用“从连接到线程、从线程到业务、从业务到数据源”的方式把一个简单 SpringBoot 项目的请求链路讲明白讲明白了这一整块并发知识也就通了。最后分享一个小习惯每个接口上线前我都用 wrk 做一轮 30 秒的小压测同时盯住 Tomcat 活跃线程数和数据库连接池活跃连接数这两个指标。数据会替你说真话它能证明你刚才所有的分析和配置是有效的也能在下一次面试举例时给你一个扎实的实战弹药。