一个 SpringBoot 项目能处理多少请求?

这篇文章带大家盘一个读者遇到的面试题哈。

根据读者转述,面试官的原问题就是:一个 SpringBoot 项目能同时处理多少请求?

不知道你听到这个问题之后的第一反应是什么。

我大概知道他要问的是哪个方向,但是对于这种只有一句话的面试题,我的第一反应是:会不会有坑?

所以并不会贸然答题,先追问一些消息,比如:这个项目具体是干什么的?项目大概进行了哪些参数配置?使用的 web 容器是什么?部署的服务器配置如何?有哪些接口?接口响应平均时间大概是多少?

这样,在几个问题的拉扯之后,至少在面试题考察的方向方面能基本和面试官达成了一致。

比如前面的面试问题,经过几次拉扯之后,面试官可能会修改为:

一个 SpringBoot 项目,未进行任何特殊配置,全部采用默认设置,这个项目同一时刻,最多能同时处理多少请求?

能处理多少呢?

我也不知道,但是当问题变成上面这样之后,我找到了探索答案的角度。

既然“未进行任何特殊配置”,那我自己搞个 Demo 出来,压一把不就完事了吗?

坐稳扶好,准备发车。

一个 SpringBoot 项目能处理多少请求?_第1张图片

Demo

小手一抖,先搞个 Demo 出来。

这个 Demo 非常的简单,就是通过 idea 创建一个全新的 SpringBoot 项目就行。

我的 SpringBoot 版本使用的是 2.7.13。

整个项目只有这两个依赖:

一个 SpringBoot 项目能处理多少请求?_第2张图片

整个项目也只有两个类,要得就是一个空空如也,一清二白。

一个 SpringBoot 项目能处理多少请求?_第3张图片

项目中的 TestController,里面只有一个 getTest 方法,用来测试,方法里面接受到请求之后直接 sleep 一小时。

目的就是直接把当前请求线程占着,这样我们才能知道项目中一共有多少个线程可以使用:

@Slf4j
@RestController
public class TestController {

    @GetMapping("/getTest")
    public void getTest(int num) throws Exception {
        log.info("{} 接受到请求:num={}", Thread.currentThread().getName(), num);
        TimeUnit.HOURS.sleep(1);
    }
}

项目中的 application.properties 文件也是空的:

一个 SpringBoot 项目能处理多少请求?_第4张图片

这样,一个“未进行任何特殊配置”的 SpringBoot 不就有了吗?

基于这个 Demo,前面的面试题就要变成了:我短时间内不断的调用这个 Demo 的 getTest 方法,最多能调用多少次?

问题是不是又变得更加简单了一点?

那么前面这个“短时间内不断的调用”,用代码怎么表示呢?

很简单,就是在循环中不断的进行接口调用就行了。

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

当然了,这个地方你用一些压测工具,比如 jmeter 啥的,会显得逼格更高,更专业。我这里就偷个懒,直接上代码了。

答案

经过前面的准备工作,Demo 和测试代码都就绪了。

接下来就是先把 Demo 跑起来:

然后跑一把 MainTest。

当 MainTest 跑起来之后,Demo 这边就会快速的、大量的输出这样的日志:

一个 SpringBoot 项目能处理多少请求?_第5张图片

也就是我前面 getTest 方法中写的日志:

一个 SpringBoot 项目能处理多少请求?_第6张图片

好,现在我们回到这个问题:

我短时间内不断的调用这个 Demo 的 getTest 方法,最多能调用多少次?

来,请你告诉我怎么得到这个问题的答案?

我这里就是一个大力出奇迹,直接统计“接受到请求”关键字在日志中出现的次数就行了:

一个 SpringBoot 项目能处理多少请求?_第7张图片

很显然,答案就是:

一个 SpringBoot 项目能处理多少请求?_第8张图片

所以,当面试官问你:一个 SpringBoot 项目能同时处理多少请求?

你装作仔细思考之后,笃定的说:200 次。

面试官微微点头,并等着你继续说下去。

你也暗自欢喜,幸好看了歪歪歪师傅的文章,背了个答案。然后等着面试官继续问其他问题。

气氛突然就尴尬了起来。

接着,你就回家等通知了。

一个 SpringBoot 项目能处理多少请求?_第9张图片

200 次,这个回答是对的,但是你只说 200 次,这个回答就显得有点尬了。

重要的是,这个值是怎么来的?

所以,下面这一部分,你也要背下来。

怎么来的?

在开始探索怎么来的之前,我先问你一个问题,这个 200 个线程,是谁的线程,或者说是谁在管理这个线程?

是 SpringBoot 吗?

肯定不是,SpringBoot 并不是一个 web 容器。

应该是 Tomcat 在管理这 200 个线程。

这一点,我们通过线程 Dump 也能进行验证:

一个 SpringBoot 项目能处理多少请求?_第10张图片

一个 SpringBoot 项目能处理多少请求?_第11张图片

通过线程 Dump 文件,我们可以知道,大量的线程都在 sleep 状态。而点击这些线程,查看其堆栈消息,可以看到 Tomcat、threads、ThreadPoolExecutor 等关键字:

at org.apache.Tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791)
at org.apache.Tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.Tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.Tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.Tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

基于“短时间内有 200 个请求被立马处理的”这个现象,结合你背的滚瓜烂熟的、非常扎实的线程池知识,你先大胆的猜一个:Tomcat 默认核心线程数是 200。

接下来,我们就是要去源码里面验证这个猜测是否正确了。

我之前分享过阅读源码的方式,《我试图通过这篇文章,教会你一种阅读源码的方式。》,其中最重要的一条就是打一个有效的断点,然后基于断点处的调用栈去定位源码。

这里我再教你一个不用打断点也能获取到调用栈的方法。

在前面已经展示过了,就是线程 Dump。

右边就是一个线程完整的调用栈:

一个 SpringBoot 项目能处理多少请求?_第12张图片

从这个调用栈中,由于我们要找的是 Tomcat 线程池相关的源码,所以第一次出现相关关键字的地方就是这一行:

org.apache.Tomcat.util.threads.ThreadPoolExecutor.Worker#run

一个 SpringBoot 项目能处理多少请求?_第13张图片

然后我们在这一行打上断点。

重启项目,开始调试。

进入 runWorker 之后,这部分代码看起来就非常眼熟了:

一个 SpringBoot 项目能处理多少请求?_第14张图片

简直和 JDK 里面的线程池源码一模一样。

如果你熟悉 JDK 线程池源码的话,调试 Tomcat 的线程池,那个感觉,就像是回家一样。

如果你不熟悉的话,我建议你尽快去熟悉熟悉。

随着断点往下走,在 getTask 方法里面,可以看到关于线程池的几个关键参数:

你可能感兴趣的:(java,开发语言)