Spring Boot 2系列(六十):Tomcat 中 NIO 模型与启动流程
以 Spring Boot 2.6.3 版本,spring-boot-starter-web 嵌入的 9.0.56 版本 tomcat-embed-core 为例,分析 Tomcat 中的 NIO 的配置与优化。
注意:Tomcat 8.5 移除了 BIO,默认启用 NIO,Tomcat 的架构和核心组件已与 Tomcat 7.x 版本已略有不同,与网上很多基于 Tomcat 7.x 版本分析的文章是不符的,需要自己走读源码。
网络I/O模型
5种网络 I/O 模型,具体可参考:网络I/O模型详解。
- 阻塞 I/O 模型
- 非阻塞 I/O 模型
- I/O 多路复用模型
- 信号驱动 I/O 模型
- 异步 I/O 模型
Tomcat 支持的 NIO 模型是基于 JDK 的 java.nio 包实现。
Tomcat NIO
网络 NIO 模型优化的是 I/O 的读写,优势是可以使用少量的线程处理大量的连接,节省线程资源(如内存),更适合需要维护大量长连接的应用场景。
Tomcat Connector
Tomcat 从 8.5 开始,HTTP 和 AJP 的 Java 阻塞 IO 实现 (BIO) 已被删除。Tomcat 建议用户切换到 Java 非阻塞 IO 实现 (NIO)[引]。
从 Tomcat 8.5.17 开始,如果显式配置了 BIO 连接器,并不会无法启动连接器,Tomcat 将自动切换连接器以使用 NIO 实现并记录警告。
Web 服务器上阻塞 IO(BIO) 与 NIO:
- BIO:通常会为每一个 Web 请求引入单独的线程,当出现高并发量的同时增加线程数,CPU 就需要忙着线程切换损耗性能,所以BIO不合适高吞吐量、高可伸缩的Web 服务器。
- NIO:使用单线程(单个CPU)或者只使用少量的多线程(多CPU)来接受Socket,而由线程池来处理阻塞在 pipe 或者队列里的请求。这样的话,只要操作系统可以接受 TCP 的连接,Web 服务器就可以处理该请求。大大提高了Web 服务器的。
Tomcat Connector 比较:
下面是,显示了连接器的不同之处。
Java Nio Connector NIO | Java Nio2 Connector NIO2 | APR/native Connector APR | |
---|---|---|---|
Classname | Http11NioProtocol | Http11Nio2Protocol | Http11AprProtocol |
Tomcat Version | since 6.0.x | since 8.0.x | since 5.5.x |
Support Polling | YES | YES | YES |
Polling Size | maxConnections | maxConnections | maxConnections |
Read Request Headers | Non Blocking | Non Blocking | Non Blocking |
Read Request Body | Blocking | Blocking | Blocking |
Write Response Headers and Body | Blocking | Blocking | Blocking |
Wait for next Request | Non Blocking | Non Blocking | Non Blocking |
SSL Support | Java SSL or OpenSSL | Java SSL or OpenSSL | OpenSSL |
SSL Handshake | Non blocking | Non blocking | Blocking |
Max Connections | maxConnections | maxConnections | maxConnections |
Tomcat Endpoint
要理解 Tomcat 的 NIO 最主要的就是对 NioEndpoint 的理解。NioEndpoint 包含以下五个组件。
LimitLatch:连接控制器,负责维护连接数计算,连接数默认是 8192,达到这个阀值后,就会拒绝连接请求。
Acceptor:负责接收连接,默认是 1 个线程执行,将包装了 NioChannel 的 NioSocket 注册到 Poller 的 PollerEvent 队列。
Poller:负责轮询 PollerEvent 队列,默认是 1 个线程执行,将就绪的事件生成 SocketProcessor 交给 Executor (Tomcat 实现的 ThreadPoolExecutor,称为内部 Executor )去执行。
SocketProcessor:此类相当于Worker,判断 Socket 握手状态和 SocketEvent 状态,对 Socket 执行读写操作,是真正的 Request 和 Response 处理类(发起类)。
Executor:Tomcat 实现的 ThreadPoolExecutor,工作线程池。核心线程数默认为 10,最大线程数默认为 200,线程存活时间 60s,使用链表阻塞队列,自定义的线程工程。
1
2
3
4
5
6
7public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}Executor 中的线程,执行 SocketProcessor 的逻辑,从 Socket 中读取 HTTP Request,然后解析成 HttpServletRequest 对象,再分派到相应的 Servlet 完成逻辑,最后将 Response 通过 Socket 发回给客户端。
从 Socket 中读和写数据,并不是在 NIO 的模式下注册读和写事件到 Selector 复用器上的,而是直接通过 Socket 来完成读写的,这个过程是阻塞的。
Tomcat 组件结构图:
Tomcat 组件时序图:
NioEndpoint 启动
1 | /** |
Spring Boot Tomcat
默认配置NIO
Spring Boot 嵌入的 Tomcat 默认启用 Http11NioProtocol
NIO,可以切换日志级别为 Debug 可看到。
1 | # 日志级别 Debug |
启动应用程序,查看启动日志。
1 | 2022-02-13 10:59:20.194 DEBUG 2816 --- [ restartedMain] o.apache.tomcat.util.IntrospectionUtils : IntrospectionUtils: setProperty(class org.apache.coyote.http11.Http11NioProtocol port=8080) |
查看源码:
Tomcat 连接器类 Connector 空参构造设置了默认协议(protocol)为 “HTTP/1.1”。
Spring 扩展的 TomcatServletWebServerFactory 类创建 WebServer 的方法 getWebServer 方法中设置了连接器,默认协义是 org.apache.coyote.http11.Http11NioProtocol。
Tomcat 类获取连接器方法 getConnector,如果没有连接器则会创建并返回一个连接器,协议是 “HTTP/1.1”。
协议处理器类 ProtocolHandler 的 create 方法中判断如果 protocol 为 “HTTP/1.1” 或为 Http11NioProtocol 的 className,则使用 org.apache.coyote.http11.Http11NioProtocol。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39/**
* 连接器
*/
public class Connector extends LifecycleMBeanBase {
/**
* Defaults to using HTTP/1.1 NIO implementation.
*/
public Connector() {
this("HTTP/1.1");
}
}
/**
* TomcatWebServer 工厂
*/
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
//..............省略................
// Connector 默认的协议(protocol)默认为 "HTTP/1.1"
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
// ........省略........
// 使用默认协议
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
// ........省略........
return this.getTomcatWebServer(tomcat);
}
}
启动流程
Spring Boot 应用都是从入口类的 main 方法启动。
Spring Boot 启动会创建 Servlet Web Server 应用上下文。
SpringApplication.run > SpringApplication.refresh > ServletWebServerApplicationContext.createWebServer。
1
2
3
4
5private void createWebServer() {
//...省略
this.webServer = factory.getWebServer(getSelfInitializer());
//...省略
}Spring Boot Web 内嵌了 Tomcat,会将 TomcatServletWebServerFactory 注册为 Bean,通过他来创建 TomcatWebServer 服务。
TomcatServletWebServerFactory.getWebServer -> new Tomcat() > new TomcatWebServer(Tomcat tomcat)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 创建Tomcat
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
// 创建连接器,设置 HTTP 协议,同时创建了协议处理器 ProtocolHandler
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
// 添加连接器
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
// 包装成 TomcatWebServer
return getTomcatWebServer(tomcat);
}
// 创建 TomcatWebServer 对象
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}创建 TomcatWebServer 对象同时执行初始化操作,才是启动 Tomcat 服务,初始化几个核心组件。
TomcatWebServer.initialize > Tomcat.start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14// TomcatWebServer构造方法
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
//...省略
// 启动
this.tomcat.start();
// 省略
}
// Tomcat.start(), server 是 StandardServer
public void start() throws LifecycleException {
getServer();
server.start();
}Tocmat.start 实际调的是 StandardServer 抽象父类的 LifecycleBase.start 方法,在此方法中调 init 方法 执行真正的初始化。
LifecycleBase 是 Tomcat 很多组件的父类,管理生命周期(状态转换)。两个核心方法:init(),startInternal()。
1
2
3
4
5
6
7
8
9
10
11
public final synchronized void start() throws LifecycleException {
//....省略
// 初始化
init();
// 启动内部
startInternal();
//....省略
}Connector 初始化:LifecycleBase.init 方法最后调用的是 Connector 的 initInternal
1
2
3
4
5
6
7
8
9
10
11
protected void initInternal() throws LifecycleException {
//....省略
try {
// 协议处理器初始化
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}实际调用的是协议的抽象父类的 AbstractProtocol.init 的初始化方法
1
2
3
4
5
6
public void init() throws Exception {
//...省略
//...endpoint 初始化
endpoint.init();
}NioEndpoint 初始化,开启 Socket 监听和绑定服务地址
调父类的初始化方法 AbstractEndpoint.init().bindWithCleanup() > NioEndpoint.bind().initServerSocket();
1
2
3
4
5
6
7
8
9protected void initServerSocket() throws Exception {
//...省略
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.bind(addr, getAcceptCount());
//...省略
serverSock.configureBlocking(true); //mimic APR behavior
}Connector 启动:LifecycleBase.startInternal方法最后调用的是 Connector 的 startInternal
1
2
3
4
5
6
7
8
9
10
11
protected void startInternal() throws LifecycleException {
//...省略
// 协议处理器启动
try {
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}实际调用的是协议抽象类的方法:AbstractProtocol.start 方法。
1
2
3
4
5
6
public void start() throws Exception {
//...省略
endpoint.start();
//...省略
}NioEndpoint 启动:判断若未绑定会执行绑定操作,启动内部组件和执行逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50//NioEndpoint start
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bindWithCleanup();
bindState = BindState.BOUND_ON_START;
}
// 启动
startInternal();
}
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
// 缓存配置
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
// 创建线程池
if (getExecutor() == null) {
createExecutor();
}
//初始化连接数线制
initializeConnectionLatch();
// 启动 Poller 线程
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
// 启动 Acceptor 线程
startAcceptorThread();
}
}
相关文章
Spring Boot 2系列(六十):Tomcat 中 NIO 模型与启动流程
http://blog.gxitsky.com/2022/02/12/SpringBoot-60-tomcat-nio/