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。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Spring Boot 常用 Tomcat 配置项:
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 容器,则只需要修改依赖,不需要修改其他配置:
<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 配置项:
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 容器,则只需要修改依赖,不需要修改其他配置:
<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 配置项:
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 |
接口方法很简单:
@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
监控的程序性能如下三图所示:
图 1\. Undertow 服务性能测试 图 2\. Tomcat 服务性能测试 图 3\. Jetty 服务性能测试
3. 总结
Undertow 的吞吐量表现最好;
Tomcat 和 Undertow 在 CPU 占用率上差距不大,但二者均明显优于 Jetty;
三者的请求耗时差别不是很大;
从结果来看,Tomcat 和 Undertow 都是比较稳定的 Servlet 容器,并不像文档中介绍的 Undertow 有明显的优势,综合来说,可以根据你的业务选型,或者项目组之前的技术栈选择自己合适的 Servlet 容器即可。
作者:zhaoyh
来源链接:
http://zhaoyh.com.cn/2020/12/10/Spring%20Boot(%E5%8D%81)%E4%B9%8B%E5%86%85%E5%B5%8C%E5%AE%B9%E5%99%A8%E5%88%86%E6%9E%90/#more