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 模型

5种网络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
    7
    public 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_NIO_Components

Tomcat 组件时序图

Tomcat_NIO_Components

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
51
52
53
54
55
56
57
58
59
/**
* Start the NIO endpoint, creating acceptor, poller threads.
*/
@Override
public void startInternal() throws Exception {

if (!running) {
running = true;
paused = false;

// 初始化 SocketProcessor 缓存, 默认 processorCache = 500
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
// 初始化 PollerEvent 缓存, 默认 eventCache = 500
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
// 初始化 NioChannel 缓存, bufferPool = 500
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}

// Create worker collection,创建线程池
if (getExecutor() == null) {
createExecutor();
}
// 初始化连接数限制
initializeConnectionLatch();

// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
// Start acceptor thread
startAcceptorThread();
}
}

// 初始化 ServerSocket,返回的是 ServerSocketChannel
protected void initServerSocket() throws Exception {
if (getUseInheritedChannel()) {
//....省略代码
} else {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
//设置允许积压的 ServerSocketChannel, 默认值为 100
serverSock.bind(addr, getAcceptCount());
}
//初始化时设置为阻塞,模仿的是 APR 行为
//实际有请求进来时会被设置为 false, 非阻塞,由轮询使用
serverSock.configureBlocking(true);
}

Spring Boot Tomcat

默认配置NIO

Spring Boot 嵌入的 Tomcat 默认启用 Http11NioProtocol NIO,可以切换日志级别为 Debug 可看到。

1
2
# 日志级别 Debug
logging.level.root=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)

查看源码:

  1. Tomcat 连接器类 Connector 空参构造设置了默认协议(protocol)为 “HTTP/1.1”。

  2. Spring 扩展的 TomcatServletWebServerFactory 类创建 WebServer 的方法 getWebServer 方法中设置了连接器,默认协义是 org.apache.coyote.http11.Http11NioProtocol。

  3. Tomcat 类获取连接器方法 getConnector,如果没有连接器则会创建并返回一个连接器,协议是 “HTTP/1.1”。

  4. 协议处理器类 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);
    }
    }

启动流程

  1. Spring Boot 应用都是从入口类的 main 方法启动。

    Spring Boot 启动会创建 Servlet Web Server 应用上下文。

    SpringApplication.run > SpringApplication.refresh > ServletWebServerApplicationContext.createWebServer。

    1
    2
    3
    4
    5
    private void createWebServer() {
    //...省略
    this.webServer = factory.getWebServer(getSelfInitializer());
    //...省略
    }
  2. 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
    @Override
    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());
    }
  3. 创建 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();
    }
  4. Tocmat.start 实际调的是 StandardServer 抽象父类的 LifecycleBase.start 方法,在此方法中调 init 方法 执行真正的初始化。

    LifecycleBase 是 Tomcat 很多组件的父类,管理生命周期(状态转换)。两个核心方法:init(),startInternal()。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    public final synchronized void start() throws LifecycleException {
    //....省略

    // 初始化
    init();
    // 启动内部
    startInternal();

    //....省略
    }
  5. Connector 初始化:LifecycleBase.init 方法最后调用的是 Connector 的 initInternal

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    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
    @Override
    public void init() throws Exception {
    //...省略
    //...endpoint 初始化
    endpoint.init();
    }
  6. NioEndpoint 初始化,开启 Socket 监听和绑定服务地址

    调父类的初始化方法 AbstractEndpoint.init().bindWithCleanup() > NioEndpoint.bind().initServerSocket();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    protected 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
    }
  7. Connector 启动:LifecycleBase.startInternal方法最后调用的是 Connector 的 startInternal

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    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
    @Override
    public void start() throws Exception {
    //...省略
    endpoint.start();
    //...省略
    }
  8. 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.
    */
    @Override
    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();
    }
    }

相关文章

  1. Tomcat架构分析(概览)
  2. Tomcat架构分析 (connector NIO 实现)
  3. Tomcat NIO 模型的实现
  4. Tomcat bio nio apr 模式性能测试
  5. Tomcat NioEndpoint的SocketProcessor
  6. 深度解读 Tomcat 中的 NIO 模型

Spring Boot 2系列(六十):Tomcat 中 NIO 模型与启动流程

http://blog.gxitsky.com/2022/02/12/SpringBoot-60-tomcat-nio/

作者

光星

发布于

2022-02-12

更新于

2024-02-22

许可协议

评论