Spring Boot(十)之内嵌容器分析

1. 应用容器介绍

在Spring Boot框架中,默认的内嵌Web应用容器是Tomcat,Tomcat诞生较早,是目前应用比较广泛的Web容器,Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page的支持。

Undertow是红帽软件旗下JBoss Community开发的轻量级高性能Servlet容器,Undertow 提供阻塞或基于XNIO的非阻塞机制。Undertow提供一个基础的架构用来构建Web服务器,完全兼容Java EE Servlet 3.1和低级非堵塞的处理器,在高并发情况下表现非常出色。

Jetty是Eclipse基金会旗下的开源Web容器,轻量级,易扩展,组件支持按需加载和可插拔,对最新Servlet规范支持响应快。

Spring Boot同时支持以上三种Servlet容器,用户可以按需选择。本文主要介绍了三种容器的切换,以及容器的核心配置,并对容器的选型做了一些对比实验。

2. 容器选择

2.1 容器切换

Spring Boot默认的内嵌Web应用容器是Tomcat,因此你只需要引入spring-boot-starter-web依赖,默认的启动项就是Tomcat。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring Boot常用Tomcat配置项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
tomcat:
#编码选择
uri-encoding: UTF-8
#最大连接数
max-connections: 10000
#最大等待队列长度
accept-count: 100
#链接建立超时时间
connection-timeout: 12000
threads:
#最大线程数
max: 200
#最小线程数
min-spare: 10
  • 线程数是指,每一个HTTP请求到达Web服务器,Web服务器都会创建一个线程来处理该请求,该参数决定了同时可以处理多少个HTTP请求;
  • min-spare最小线程数是Tomcat启动时的初始化的线程数,max是维持的最大线程数,同时超过这个请求数后,客户端请求只能排队,等有线程释放才能处理;
  • 当HTTP并发请求数达到最大线程数时,若有新的HTTP请求到来,这时Tomcat会将该请求放在等待队列中,这个accept-count就是指能够接受的最大等待数,如果等待队列也满了,这个时候再来新的请求就会被Tomcat拒绝(connection refused);
  • 最大连接数max-connections,Tomcat在任意时刻接收和处理的最大连接数;
  • 最长等待时间,如果没有数据进来,等待一段时间后断开连接,释放线程,参数主要用于控制和客户端之间的连接的超时时间,有些恶意的客户端在建立完TCP连接之后不发送任何HTTP请求,服务器如果不对这种行为进行有效管控,则很快就会消耗完所有线程池资源,无法进行服务,该参数就是用来控制连接建立后在多长时间内服务器可以主动关闭连接的;

若切换为Undertow容器,则只需要修改依赖,不需要修改其他配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

Spring Boot常用Undertow配置项:

1
2
3
4
5
6
7
server:
undertow:
threads:
io: 200
worker: 400
direct-buffers: true
buffer-size: 64MB
  • threads.io,设置IO线程数,它主要执行非阻塞的任务,它们会负责多个连接,默认设置每个CPU核心一个线程,不要设置过大;
  • threads.worker,阻塞任务线程池,当执行servlet请求阻塞IO操作时,Undertow会从这个线程池中取得线程,默认值是io线程数*8;
  • direct-buffers,是否分配直接内存;
  • buffer-size,每块buffer的空间大小,空间越小利用越充分,不需要设置太大;

若切换为Jetty容器,则只需要修改依赖,不需要修改其他配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

Spring Boot常用Jetty配置项:

1
2
3
4
5
6
7
8
9
server:
jetty:
threads:
min: 10
max: 200
max-queue-capacity: 200
acceptors: 2
selectors: 4
idle-timeout: 60000ms
  • threads.min,最小处理线程数;
  • threads.max,最大处理线程数;
  • threads.max-queue-capacity,等待队列长度;
  • thread.acceptors,负责接受新连接,非阻塞;
  • threads.selectors,处理HTTP消息协议的解包,最后由工作线程处理请求,非阻塞;
  • threads.idle-timeout,空闲等待时间;

2.2 对比实验

为了对比三个Servlet容器的性能,笔者做了简单测试,主要测试了容器的下行字符串的处理能力,其中本机测试环境参考如下:

环境
软件环境 Mac OS Big Sur + Spring Boot 2.4.0 + jdk1.8
并发变量 并发数150,请求总数2000

接口方法很简单:

1
2
3
4
5
6
@GetMapping(value = {"/", "/index"})
public JSONObject index() {
JSONObject json = new JSONObject();
json.put("date", LocalDate.now());
return json;
}

吞吐量和请求耗时测试结果如下:

Web容器 吞吐量/sec 单位耗时/ms
Undertow 3206 0.37
Tomcat 3022 0.35
Jetty 2997 0.34

JConsole监控的程序性能如下三图所示:

image

图1. Undertow服务性能测试

image

图2. Tomcat服务性能测试

image

图3. Jetty服务性能测试

3. 总结

  • Undertow的吞吐量表现最好;
  • Tomcat和Undertow在CPU占用率上差距不大,但二者均明显优于Jetty;
  • 三者的请求耗时差别不是很大;

从结果来看,Tomcat和Undertow都是比较稳定的Servlet容器,并不像文档中介绍的Undertow有明显的优势,综合来说,可以根据你的业务选型,或者项目组之前的技术栈选择自己合适的Servlet容器即可。

以上内容就是关于Spring Boot(十)之内嵌容器分析的全部内容了,谢谢你阅读到了这里!

Author:zhaoyh