写点什么

面试官:SpringBoot 可以同时处理多少请求?

  • 2023-05-27
    湖南
  • 本文字数:2085 字

    阅读完需:约 7 分钟

前两天面试的时候,面试官问我:一个 ip 发请求过来,是一个 ip 对应一个线程吗?我突然愣住了,对于 SpringBoot 如何处理请求好像从来没仔细思考过,所以面试结束后就仔细研究了一番,现在就来探讨一下这个问题。


我们都知道,SpringBoot 默认的内嵌容器是 Tomcat,也就是我们的程序实际上是运行在 Tomcat 里的。所以与其说 SpringBoot 可以处理多少请求,到不如说 Tomcat 可以处理多少请求。


关于 Tomcat 的默认配置,都在spring-configuration-metadata.json文件中,对应的配置类则是org.springframework.boot.autoconfigure.web.ServerProperties

和处理请求数量相关的参数有四个:

  • server.tomcat.threads.min-spare:最少的工作线程数,默认大小是 10。该参数相当于长期工,如果并发请求的数量达不到 10,就会依次使用这几个线程去处理请求。

  • server.tomcat.threads.max:最多的工作线程数,默认大小是 200。该参数相当于临时工,如果并发请求的数量在 10 到 200 之间,就会使用这些临时工线程进行处理。

  • server.tomcat.max-connections:最大连接数,默认大小是 8192。表示 Tomcat 可以处理的最大请求数量,超过 8192 的请求就会被放入到等待队列。

  • server.tomcat.accept-count:等待队列的长度,默认大小是 100。


举个例子说明一下这几个参数之间的关系:

如果把 Tomcat 比作一家饭店的话,那么一个请求其实就相当于一位客人。min-spare 就是厨师(长期工);max 是厨师总数(长期工+临时工);max-connections 就是饭店里的座位数量;accept-count 是门口小板凳的数量。来的客人优先坐到饭店里面,然后厨师开始忙活,如果长期工可以干的完,就让长期工干,如果长期工干不完,就再让临时工干。


图中画的厨师一共 15 人,饭店里有 30 个座位,也就是说,如果现在来了 20 个客人,那么就会有 5 个人先在饭店里等着。如果现在来了 35 个人,饭店里坐不下,就会让 5 个人先到门口坐一下。如果来了 50 个人,那么饭店座位+门口小板凳一共 40 个,所以就会有 10 人离开。


也就是说,SpringBoot 同时所能处理的最大请求数量是max-connections+accept-count,超过该数量的请求直接就会被丢掉。


纸上得来终觉浅,绝知此事要躬行。


上面只是理论结果,现在通过一个实际的小例子来演示一下到底是不是这样:


创建一个 SpringBoot 的项目,在 application.yml 里配置一下这几个参数,因为默认的数量太大,不好测试,所以配小一点:

 server:   tomcat:     threads:       # 最少线程数       min-spare: 10       # 最多线程数       max: 15     # 最大连接数     max-connections: 30     # 最大等待数     accept-count: 10
复制代码

再来写一个简单的接口:

     @GetMapping("/test")     public Response test1(HttpServletRequest request) throws Exception {         log.info("ip:{},线程:{}", request.getRemoteAddr(), Thread.currentThread().getName());         Thread.sleep(500);         return Response.buildSuccess();     }
复制代码

代码很简单,只是打印了一下线程名,然后休眠 0.5 秒,这样肯定会导致部分请求处理一次性处理不了而进入到等待队列。


然后我用 Apifox 创建了一个测试用例,去模拟 100 个请求:

观察一下测试结果:

从结果中可以看出,由于设置的 max-connections+accept-count 的和是 40,所以有 60 个请求会被丢弃,这和我们的预期是相符的。由于最大线程是 15,也就是有 25 个请求会先等待,等前 15 个处理完了再处理 15 个,最后在处理 10 个,也就是将 40 个请求分成了 15,15,10 这样三批进行处理。

再从控制台的打印日志可以看到,线程的最大编号是 15,这也印证了前面的想法。


总结一下:如果并发请求数量低于 server.tomcat.threads.max,则会被立即处理,超过的部分会先进行等待,如果数量超过 max-connections 与 accept-count 之和,则多余的部分则会被直接丢弃。

延伸:并发问题是如何产生的

到目前为止,就已经搞明白了 SpringBoot 可以同时处理多少请求的问题。但是在这里我还想基于上面的例子再延伸一下,就是为什么并发场景下会出现一些值和我们预期的不一样?


设想有以下场景:厨师们用一个账本记录一共做了多少道菜,每个厨师做完菜都记录一下,每次记录都是将账本上的数字先抄到草稿纸上,计算 x+1 等于多少,然后将计算的结果写回到账本上。

Spring 容器中的 Bean 默认是单例的,也就是说,处理请求的 Controller、Service 实例就只有一份。在并发场景下,将 cookSum 定义为全局变量,是所有线程共享的,当一个线程读到了 cookSum=20,然后计算,写回前另一个线程也读到是 20,两个线程都加 1 后写回,最终 cookSum 就变成了 21,但是实际上应该是 22,因为加了两次。

 private int cookSum = 0; ​ @GetMapping("/test") public Response test1(HttpServletRequest request) throws Exception {     // 做菜。。。。。。     cookSum += 1;     log.info("做了{}道菜", cookSum);     Thread.sleep(500);     return Response.buildSuccess(); }
复制代码


如果要避免这样的情况发生,就涉及到加锁的问题了,就不在这里讨论了。


作者:Robod

链接:https://juejin.cn/post/7203648441721126972

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
面试官:SpringBoot可以同时处理多少请求?_Java_做梦都在改BUG_InfoQ写作社区