springboot:2.3.12.RELEASE中内嵌的tomcat-embed-core:9.0.46为例,进行分析

1 概述

1.0 关键依赖包

  • spring-boot-autoconfigure : 2.3.12.RELEASE
  • spring-boot : 2.3.12.RELEASE
  • spring-context : 5.2.15.RELEASE
  • spring-webmvc : 5.2.15.RELEASE
  • tomcat-embed-core:9.0.46
  • tomcat-embed-jasper:9.0.46

1.1 内嵌 Web Server 的优势

我们在使用 springboot 开发 web 项目时,大多数时候采用的是内置的 Tomcat (当然也可配置支持内置的 jett y),内置 Tomcat 有什么好处呢?

  • 方便微服务部署,减少繁杂的配置
  • 方便项目启动,不需要单独下载web容器,如Tomcat,jetty等。

1.2 Web Server 的优化思路

针对目前的容器优化,可以从以下几点考虑:

  • 1、线程数

首先,线程数是一个重点,每一次HTTP请求到达Web服务器,Web服务器都会创建一个线程来处理该请求,该参数决定了应用服务同时可以处理多少个HTTP请求。

比较重要的有两个:1) 初始线程数; 2) 最大线程数。

  • 初始线程数:保障启动的时候,如果有大量用户访问,能够很稳定的接受请求。
  • 最大线程数:用来保证系统的稳定性。
  • 2、超时时间

超时时间:用来保障连接数不容易被压垮。

如果大批量的请求过来,延迟比较高,很容易把线程数用光,这时就需要提高超时时间。

这种情况在生产中是比较常见的 ,一旦网络不稳定,宁愿丢包也不能把服务器压垮。

  • 3、JVM优化

1.3 Tomcat Web Server的核心配置参数

min-spare-threads

默认 10

最小备用线程数,tomcat启动时的初始化的线程数。

max-threads

默认 200

Tomcat可创建的最大的线程数,每一个线程处理一个请求;

超过这个请求数后,客户端请求只能排队,等有线程释放才能处理。

建议:这个配置数可以在服务器CUP核心数的 200~250 倍之间

accept-count

默认 100

当调用Web服务的HTTP请求数达到tomcat的最大线程数时,还有新的HTTP请求到来,这时tomcat会将该请求放在等待队列中

这个acceptCount就是指能够接受的最大等待数

如果等待队列也被放满了,这个时候再来新的请求就会被tomcat拒绝(connection refused)。

max-connections

这个参数是指在同一时间,tomcat能够接受的最大连接数。(最大线程数+排队数)

一般这个值要大于 (max-threads)+(accept-count)。

connection-timeout

1 默认值: 60S or 20S

2 参数定义: 与客户端建立连接后,Tomcat 等待客户端请求的时间。 如果客户端没有请求进来,等待一段时间后断开连接,释放线程。

3 备注说明: Tomcat 中 等效于 : socket.soTimeout (SO_TIMEOUT) => 即: 为 socket 调用 read() 等待读取的时间

4 入口类:

keepAliveTimeout

Tomcat 在关闭连接(Connector)之前,等待另一个请求的时间

  • HTTP 1.0

http协议的早期是,每开启一个http链接,是要进行一次socket,也就是新启动一个TCP链接。

  • HTTP 1.1

1 特性:长连接 (现主流浏览器的默认协议)

2 使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数。

3 如果浏览器支持keepalive的话,那么请求头中会有: Connection: Keep-Alive

4 对于keepalive的部分,主要集中在Connection属性当中,这个属性可以设置两个值:

  • close (告诉WEB服务器或者代理服务器,在完成本次请求的响应后,断开连接,不要等待本次连接的后续请求了)。
  • keepalive (告诉WEB服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求)。

    5 keep-alive与TIME_WAIT的关系?
  • 使用http keep-alive,可以减少服务端TIME_WAIT数量(因为由服务端httpd守护进程主动关闭连接)。道理很简单,相较而言,启用keep-alive,建立的tcp连接更少了,自然要被关闭的tcp连接也相应更少了。
  • 什么是TIME_WAIT呢?
    • 通信双方建立TCP连接后,主动关闭连接的一方就会进入TIME_WAIT状态。
    • 客户端主动关闭连接时,会发送最后一个ack后,然后会进入TIME_WAIT状态,再停留2个MSL时间,进入CLOSED状态。
  • 那么这个TIME_WAIT到底有什么作用呢?主要原因:
    • a)可靠地实现TCP全双工连接的终止
    • b)允许老的重复分节在网络中消逝

      6 截止目前,我们讨论的是 http 1.1 request/response header 的 keep-alive 选项;而 tcp协议 也有keepalive的概念。
http keep-alive与tcp keep-alive,不是同一回事,意图不一样。

http keep-alive是为了让tcp活得更久一点,以便在同一个连接上传送多个http,提高socket的效率。

而tcp keep-alive是TCP的一种检测TCP连接状况的保鲜机制。

tcp keep-alive保鲜定时器,支持三个系统内核配置参数:
echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes keepalive是TCP保鲜定时器,当网络两端建立了TCP连接之后,闲置idle(双方没有任何数据流发送往来)了tcp_keepalive_time后,服务器内核就会尝试向客户端发送侦测包,来判断TCP连接状况(有可能客户端崩溃、强制关闭了应用、主机不可达等等)。如果没有收到对方的回答(ack包),则会在 tcp_keepalive_intvl后再次尝试发送侦测包,直到收到对对方的ack,如果一直没有收到对方的ack,一共会尝试 tcp_keepalive_probes次,每次的间隔时间在这里分别是15s, 30s, 45s, 60s, 75s。如果尝试tcp_keepalive_probes,依然没有收到对方的ack包,则会丢弃该TCP连接。TCP连接默认闲置时间是2小时,一般设置为30分钟足够了。
总结一下,实际上tcp keep-alive是一个协议级别的心跳检测实现,当超过规定的时间,tcp就断开,而这边是讨论的http的keepalive,描述的http高层多次tcp链接共享,根本不是一个网络层级的东西,一定注意不要混淆。

1.4 springboot --> tomcat

spring-boot-autoconfigure : 2.3.12.RELEASE

-> org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
@ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
public static class TomcatWebServerFactoryCustomizerConfiguration { [*]
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties){
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
} -> org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer
+ 关系: public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered { /** ... **/ }
+ 属性:
private final Environment environment;
private final org.springframework.boot.autoconfigure.web.ServerProperties serverProperties; [*]
+ 方法:
public void customize(ConfigurableTomcatWebServerFactory factory) {
ServerProperties properties = this.serverProperties;
ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
--> Tomcat { // 内部类
private final Threads threads = new Threads();
...
private int maxConnections;
private int acceptCount;
...
private Duration connectionTimeout;
...
private Charset uriEncoding;
--> Threads { // 内部类
private int max = 200;
private int minSpare = 10;
}
}
PropertyMapper propertyMapper = PropertyMapper.get(); ServerProperties.Tomcat.Threads threadProperties = tomcatProperties.getThreads();
...
propertyMapper.from(threadProperties::getMax).when(this::isPositive).to((maxThreads) -> {
this.customizeMaxThreads(factory, threadProperties.getMax());
});
...
propertyMapper.from(threadProperties::getMinSpare).when(this::isPositive).to((minSpareThreads) -> {
this.customizeMinThreads(factory, minSpareThreads);
});
...
propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes).when((maxHttpFormPostSize) -> {
return maxHttpFormPostSize != 0;
}).to((maxHttpFormPostSize) -> {
this.customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize);
});
...
propertyMapper.from(tomcatProperties::getAccesslog).when(ServerProperties.Tomcat.Accesslog::isEnabled).to((enabled) -> {
this.customizeAccessLog(factory);
});
...
propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);
...
propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull().to((connectionTimeout) -> {
this.customizeConnectionTimeout(factory, connectionTimeout);
});
...
propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive).to((maxConnections) -> {
this.customizeMaxConnections(factory, maxConnections);
});
...
propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive).to((acceptCount) -> {
this.customizeAcceptCount(factory, acceptCount);
});
} private void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory, int acceptCount) {
factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol<?> protocol = (AbstractProtocol)handler;
protocol.setAcceptCount(acceptCount);
} }});
}
...
private void customizeMaxConnections(ConfigurableTomcatWebServerFactory factory, int maxConnections) {
factory.addConnectorCustomizers(new TomcatConnectorCustomizer[]{(connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol<?> protocol = (AbstractProtocol)handler;
protocol.setMaxConnections(maxConnections);
} }});
}
...

X 参考文献

[Web Server]Tomcat调优之SpringBoot内嵌Tomcat源码分析的更多相关文章

  1. Springboot 加载配置文件源码分析

    Springboot 加载配置文件源码分析 本文的分析是基于springboot 2.2.0.RELEASE. 本篇文章的相关源码位置:https://github.com/wbo112/blogde ...

  2. SpringBoot内嵌Tomcat开启APR模式(运行环境为Centos7)

    网上查到的一些springboot内嵌的tomcat开启apr的文章,好像使用的springboot版本较老,在SpringBoot 2.0.4.RELEASE中已经行不通了.自己整理了一下,供参考. ...

  3. 查看和指定SpringBoot内嵌Tomcat的版本

    查看当前使用的Tomcat版本号 Maven Repository中查看 比如我们需要查Spring Boot 2.1.4-RELEASE的内嵌Tomcat版本, 可以打开链接: https://mv ...

  4. Springboot中注解@Configuration源码分析

    Springboot中注解@Configuration和@Component的区别 1.先说结论,@Configuration注解上面有@Component注解,所以@Component有的功能@Co ...

  5. springBoot从入门到源码分析

    先分享一个springBoot搭建学习项目,和springboot多数据源项目的传送门:https://github.com/1057234721/springBoot 1. SpringBoot快速 ...

  6. Spring Boot启动过程(五):Springboot内嵌Tomcat对象的start

    标题和上一篇很像,所以特别强调一下,这个是Tomcat对象的. 从TomcatEmbeddedServletContainer的this.tomcat.start()开始,主要是利用Lifecycle ...

  7. maven打包排除spring-boot内嵌tomcat容器依赖jar

    在pom文件中添加打包排除配置信息. <plugin> <artifactId>maven-war-plugin</artifactId> <version& ...

  8. SpringBoot拦截器及源码分析

    1.拦截器是什么 java里的拦截器(Interceptor)是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止 ...

  9. [Web Server]Tomcat调优之工作原理、线程池/连接池

    1 Tomcat 概述 Tomcat隶属于Apache基金会,是开源的轻量级Web应用服务器,使用非常广泛. 1.0 Tomcat 容器与原理 1.0.1 Tomcat组件构成 注意,如图所示,阴影部 ...

随机推荐

  1. nodejs 配置国内镜像

    npm config set registry https://registry.npm.taobao.org npm config set disturl https://npm.taobao.or ...

  2. 一键搭建dns

    #!/bin/bash DOMAIN=wang.orgHOST=wwwHOST_IP=10.0.0.100LOCALHOST=`hostname -I | awk '{print $1}'` . /e ...

  3. 3DMAX2023卸载方法,如何完全彻底卸载删除清理干净3dmax各种残留注册表和文件?【转载】

    3dmax2023卸载重新安装方法,使用清理卸载工具箱完全彻底删除干净3dmax2023各种残留注册表和文件.3dmax2023显示已安装或者报错出现提示安装未完成某些产品无法安装的问题,怎么完全彻底 ...

  4. Redis 集群模式的安装与配置【源码安装redis-7.0.5】

    Redis最新版下载地址:http://download.redis.io/releases/redis-7.0.5.tar.gz 步骤如下: 1)wget http://download.redis ...

  5. login_while

    # -*- coding: utf-8 -*- # @Time : 2020/7/25 22:45 # @Author : Breeze # @FileName: login_while.py use ...

  6. OSIDP-I/O管理和磁盘调度-11

    I/O设备 I/O设备分类:人可读.机器可读和远程通信三类. I/O设备之间的差别: 1.数据传送速率 2.应用领域 3.控制的复杂性 4.传送单位 5.数据表示形式 6.错误条件 I/O功能的组织 ...

  7. Oracle11g 修改数据文件路径的方法

    Oracle 修改数据文件路径的方法   1. 关闭数据库,然后启动至mount状态 sqlplus / as sysdba shutdown immediate startup mount 2. 修 ...

  8. new一个实例的原理及过程

    前提,要明白new出来的实例是什么,包含了哪些内容? 请看一下举例代码↓↓↓↓ function Person(name,age){ this.name = name; this.age = age; ...

  9. idea中新建java类

    project是项目,一个大目录,里面可以放多个module project里面存放: .idea文件(project相当于workplace) module(模块) out(编译生成的.class文 ...

  10. 关于 layui 弹出一个 DOM 表单的问题

    案例: 假设用  layer.msg 去弹出一个dom表单: 由官方文档可知,应该定义一个div,设置其 id 为某个值,然后写在 content 中: layer弹层组件开发文档 - Layui 但 ...