解决SpringBoot内嵌Tomcat并发容量的问题

 更新时间:2021年06月04日 14:36:05   作者:北冥有鱼  
这篇文章主要介绍了解决SpringBoot内嵌Tomcat并发容量的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

一、SpringBoot内嵌Tomcat默认配置与优化

在做一个关于秒杀系统的模块,进行Jmeter压测性能的时候发现tomcat并发上不去,深入原因找到可供优化的地方,力求最大性能。

发现并发容器问题

对单接口进行6000线程压测,每个线程请求5次,线程在5秒内创建完毕,当进行一半的时候,已经出现了请求响应时间过大及其错误率达到了43%。这个并发容量对于配置比较好点的服务器相对来说有点弱。

深入SpringBoot底层了解原因

在SpringBoot官方文档中提到了关于元数据的配置

可以看到,关于我们最常用的设置项目的端口的默认配置就在其中。

默认内嵌Tomcat配置

1、server.tomcat.accept-count:等待队列长度,当可分配的线程数全部用完之后,后续的请求将进入等待队列等待,等待队列满后则拒绝处理,默认100。

2、server.tomcat.max-connections:最大可被连接数,默认10000

3、server.tomcat.max-threads:最大工作线程数,默认200,

4、server.tomcat.min-spare-threads:最小工作线程数,初始化分配线程数,默认10

默认配置下,连接超过10000后会出现拒绝连接情况

默认配置下,触发的请求超过200+100后拒绝处理(最大工作线程数+等待队列长度)

这些元数据Spring当然提供了外部配置功能

#更改内嵌tomcat参数
server.port=8080
## 等待队列长度,默认100。
server.tomcat.accept-count=1000
## 最大工作线程数,默认200。(4核8g内存,线程数经验值800,操作系统做线程之间的切换调度是有系统开销的,所以不是越多越好。)
server.tomcat.max-threads=800
## 最小工作空闲线程数,默认10。(适当增大一些,以便应对突然增长的访问量)
server.tomcat.min-spare-threads=100

SpringBoot内置Tomcat,在默认设置中,Tomcat的最大线程数是200,最大连接数是10000。支持的并发量是指连接数,200个线程如何处理10000条连接的?

目前Tomcat有三种处理连接的模式,一种是BIO,一个线程只处理一个连接,另一种就是NIO,一个线程处理多个连接。由于HTTP请求不会太耗时,而且多个连接一般不会同时来消息,所以一个线程处理多个连接没有太大问题。

还有一种是apr模式,后续会详细介绍一下tomcat的这三种模式,这里不做深入讨论。

Tomcat启动的时候,可以通过log看到Connector使用的是哪一种运行模式:

Starting ProtocolHandler ["http-bio-8080"]
Starting ProtocolHandler ["http-nio-8080"]
Starting ProtocolHandler ["http-apr-8080"]

默认值可以在spring-boot-autoconfigure-版本号.jar(比如:spring-boot-autoconfigure-2.1.0.RELEASE) 包中看到,解压反编译/web/ServerProperties.class文件就可以看到默认配置。

定制化内嵌Tomcat开发

关于KeepAlive

在使用Jmeter的Http请求中默认是开启KeepAlive的

Http的KeepAlive请求为当我们的客户端向我们的服务器发送Http请求的时候,若带上了KeepAlive的请求头,则表明我们的Http客户端希望跟服务端之间建立一个KeepAlive的连接,这个连接对应的用处就是说,向我们的服务端发送完对应的响应之后,我们的服务端不要立马断开连接,而是等待尝试复用连接。

此解决方案是用来解决Http的一个响应,无状态,每次都要断开连接,新建连接所带来的一个耗时问题。

但如果说我们每个网页请求打开之后都跟服务端保持一个长连接,那我们服务端的连接数很快就会被用完了,因此再最早的Http1.0的时候是没有设计KeepAlive的请求的,但是现在的Http1.1加上KeepAlive请求,目的就是越来越多的移动端的设备,甚至于一些很复杂的网页交互,需要在用户浏览的过程当中,频繁的向服务端发送请求,因此,建立一个KeepAlive连接,并非为了压测的目的,而是真正的在应用场景上是有一些性能的好处的,无论是客户端还是服务端,在做一些网络通信的交互上面,无需每次都新建连接,断开连接,耗费Tcp/Ip建连的时间,而仅仅只需要发送数据即可。

但是这样的设计也会带来一些问题,如果说我们的服务端对KeepAlive的操作没有做任何限制 1.连接不做任何操作,不做任何响应,那这条连接对服务端来说就是一条费连接 2.有一些攻击者恶意利用KeepAlive连接向我们的服务端发送DDOS的攻击,那服务端对应的连接只会成为攻击者攻击的后门,因此,为了安全,我们需要定制化Tomcat开发

配置

1、KeepAliveTimeOut:多少毫秒后客户端不响应则断开KeepAlive

2、maxKeepAliveRequests:多少次请求后KeepAlive断开失效

在SpringBoot官方文档中提到了对内嵌容器的配置

//当spring容器内没有TomcatEmbeddedServletContainerFactory这个bean时,会把bean加载进spring容器
@Configuration
public class WebServerConfiguration implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
  //使用对应工厂类提供给我们的接口定制化我们的tomcat connector
  ((TomcatServletWebServerFactory)factory).addConnectorCustomizers(new TomcatConnectorCustomizer() {
    @Override
    public void customize(Connector connector) {
      Http11NioProtocol protocol= (Http11NioProtocol) connector.getProtocolHandler();
      //定制KeepAliveTimeout,设置30秒内没有请求则服务器自动断开keepalive连接
      protocol.setKeepAliveTimeout(30000);
      //当客户端发送超过10000个请求则自动断开keepalive连接
      protocol.setMaxKeepAliveRequests(10000);
    }
  });
}
}

容器问题优化

响应时间变长,TPS上不去

单Web容器上限

线程数量:

4核CPU 8G内存单进程调用线程数800-1000,1000以上后即花费巨大的时间在CPU调度上

等待队列长度:

队列做缓冲池用,但也不能无限长,消耗内存,出队入队也耗CPU

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot+Mybatis-Plus实现mysql读写分离方案的示例代码

    SpringBoot+Mybatis-Plus实现mysql读写分离方案的示例代码

    这篇文章主要介绍了SpringBoot+Mybatis-Plus实现mysql读写分离方案的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Java 获取 jar包以外的资源操作

    Java 获取 jar包以外的资源操作

    这篇文章主要介绍了Java 获取 jar包以外的资源操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • SpringBoot集成MQTT示例详解

    SpringBoot集成MQTT示例详解

    这篇文章主要为大家介绍了SpringBoot集成MQTT示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Spring Security注解失效的五大陷阱与避坑指南(你踩几个坑)

    Spring Security注解失效的五大陷阱与避坑指南(你踩几个坑)

    本文解析SpringSecurity注解权限控制的五大陷阱,如未启用@EnableMethodSecurity、自调用绕过代理、异常静默处理等,提醒开发者需正确配置和测试,确保权限逻辑有效执行,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • java中的接口能够被实例化吗

    java中的接口能够被实例化吗

    这篇文章主要介绍了java中的接口能够被实例化吗,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • 如何解决IDEA git拉取代码后本地代码莫名消失的问题

    如何解决IDEA git拉取代码后本地代码莫名消失的问题

    这篇文章主要介绍了如何解决IDEA git拉取代码后本地代码莫名消失的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • Java中Map与对象之间互相转换的几种常用方式

    Java中Map与对象之间互相转换的几种常用方式

    在Java中将对象和Map相互转换是常见的操作,可以通过不同的方式实现这种转换,下面这篇文章主要给大家介绍了关于Java中Map与对象之间互相转换的几种常用方式,需要的朋友可以参考下
    2024-01-01
  • Java中static关键字的作用解析

    Java中static关键字的作用解析

    这篇文章主要介绍了Java中static关键字的作用解析,Java 中,不能在所有类之外定义全局变量,只能通过在一个类中定义公用、静态的变量来实现一个全局变量,需要的朋友可以参考下
    2023-11-11
  • Java+JFrame实现贪吃蛇小游戏

    Java+JFrame实现贪吃蛇小游戏

    这篇文章主要为大家详细介绍了Java+JFrame实现贪吃蛇小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • Window中安装构建神器Jenkins详解

    Window中安装构建神器Jenkins详解

    Jenkins是一款开源 CI&CD 软件,用于自动化各种任务,包括构建、测试和部署软件。支持各种运行方式,可通过系统包、Docker 或者通过一个独立的 Java 程序。是解放人工集成部署的自动化构建神器
    2021-07-07

最新评论