欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

tomcat启动流程分析

程序员文章站 2022-07-14 10:49:54
...

前言

tomcat究竟在启动的时候做了哪些事情呢?从直观上讲,当tomcat启动完毕后,部署在tomcat里的项目,就可以通过外部的http形式进行访问了,但有个疑问是,为什么可以访问呢?其内部都做了哪些准备工作呢?本篇通过源码来了解一下tomcat启动的过程吧

通过上一篇的分析,我们初步了解了tomcat内部的基本构成,包括的基本组件,这些组件的配合构成了tomcat的逻辑上的架构

从大的方面划分,tomcat在启动过程中,主要完成了2件事情,第一初始化容器组件,第二启动相关的线程等待读写事件的接入

tomcat 的IO模型
tomcat启动流程分析

从tomcat8之后,tomcat默认使用的是NIO,即异步IO,我们知道,在tomcat8之前的版本中有大多使用同步IO,同步IO和异步IO相比,在高并发的请求处理场景中性能相差非常大,这个得益于NIO的底层特殊的线程处理机制,即reactor模型

reactor模型

简单解释来说,reactor模型将一个请求的处理过程划分成不同的步骤,在bio中,一个请求过来了,tomcat提供一个线程处理,如果请求没有处理完毕,这个线程将一直阻塞,但通过NIO的方式,将工作线程和接收请求的线程分开,并通过非阻塞的IO去处理,就大大提升了整体的性能和吞吐量

小编从实际的压测结果来看,使用tomcat7的BIO的方式,和tomcat8的NIO的方式在相同条件下进行压测,tomcat8的吞吐量比tomcat7要多出1.5倍,这只是在本地环境模拟的效果

tomcat启动流程分析

tomcat8默认使用的是NIO的方式,在server.xml中可以找到下面的这段配置,即在HTTP/1.1的情况下

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

我们可以修改protocol属性使用NIO2 (异步非阻塞)


<Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
               connectionTimeout="20000"
               redirectPort="8443" />

简单了解了tomcat8的IO模型,我们正式来看tomcat的启动流程,初始化各组件+线程就绪,即init+start

关于init过程

回忆上一篇我们谈到的关于tomcat的基本组件的构成,见下面这张图

tomcat启动流程分析

其实不难理解,tomcat在启动过程中,也是按照server.xml中各个组件标签的顺序依次进行初始化的(各个组件的作用见上一篇)

tomcat启动流程分析
其大概的步骤如下:

  1. 调用 bin/startup.bat (在linux 目录下 , 需要调用 bin/startup.sh) , 在startup.bat 脚本中, 调用了catalina.bat
  2. 在catalina.bat 脚本文件中,调用了BootStrap 中的main方法
  3. 在BootStrap 的main 方法中调用了 init 方法 , 来创建Catalina 及 初始化类加载器
  4. 在BootStrap 的main 方法中调用了 load 方法 , 在其中又调用了Catalina的load方法
  5. 在Catalina 的load 方法中 , 需要进行一些初始化的工作, 并需要构造Digester 对象, 用 于解析 XML
  6. 然后在调用后续组件的初始化操作(见时序图中的组件init过程)
  7. 加载Tomcat的配置文件,初始化容器组件 ,监听对应的端口号, 准备接受客户端请求

源码分析,从程序中的Bootstrap入手,
tomcat启动流程分析

我们找到main方法
tomcat启动流程分析

在学习源码之前,我们有必要先简单了解一下时序图中主要组件的类结构,由于所有的组件均存在初始化、启动、停止等生命周期方法,拥有生命周期管理的特 性, 所以Tomcat在设计的时候, 基于生命周期管理抽象成了一个接口 Lifecycle ,而组 件 Server、Service、Container、Executor、Connector 组件 , 都实现了一个生命周期 的接口,从而具有了以下生命周期中的核心方法:

  • init():初始化组件
  • start():启动组件
  • stop():停止组件
  • destroy():销毁组件
    tomcat启动流程分析
    tomcat启动流程分析

我们不妨随意找一个组件,像connector来说,其*接口还是Lifecycle
tomcat启动流程分析

各组件的默认实现

上面我们提到的Server、Service、Engine、Host、Context都是接口。当前对于 Endpoint组件来说,在Tomcat中没有对应的Endpoint 接口, 但是有一个抽象类 AbstractEndpoint ,其下有三个实现类: NioEndpoint、 Nio2Endpoint、AprEndpoint , 这三个实现类,分别对应于前面讲解链接器 Coyote 时, 提到的链接器支持的三种IO模型:NIO,NIO2,APR , Tomcat8.5版本中,默认采 用的是 NioEndpoint

Server
tomcat启动流程分析

Service
tomcat启动流程分析

Engine
tomcat启动流程分析
Host
tomcat启动流程分析
Context
tomcat启动流程分析

从各个组件的类的结构图也可以看出,它们都纳管于Lifecycle的生命周期的过程

源码启动入口

Bootstrap ->main()

tomcat启动流程分析
从main方法入手,可以分为2步,第一步init()的过程,即初始化上述时序图中各个组件的过程

第二步,start()过程,调其reactor涉及到的各个线程,下面通过断点简单看其中几个组件的init()过程吧

tomcat启动流程分析

调起catalina的getServer().init()方法
tomcat启动流程分析

tomcat启动流程分析
调起server的init()
tomcat启动流程分析

在initInternal的init方法中,可以看到像globalNamingResources这样的方法初始化,其实就是在依次解析server.xml中的标签
tomcat启动流程分析

来到本方法最后一段,看到调用了service的init()方法
tomcat启动流程分析

tomcat启动流程分析
tomcat启动流程分析

调起service的init()方法,该方法中要初始化的组件比较多,对应于server.xml中的各个层次的标签,举例来说,一个service里面可以配置多个connector,那么就需要在这里进行初始化
tomcat启动流程分析

跳过中间的engine,host和context,我们最后直接来到connector的初始化
tomcat启动流程分析
调起AbstractProtocol的init()方法
tomcat启动流程分析
最终来到endpoint的init()
tomcat启动流程分析

这段代码中,我们似乎看到了socket的痕迹,在前一篇提到,EndPoint是 Coyote的通信端点,即通信监听的接口,是具体Socket接收和发送处理 器是对传输层的抽象,因此EndPoint用来实现TCP/IP协议的

如果继续点进bind方法,可以看到其实就是在创建并绑定相应的连接信息
tomcat启动流程分析

那么到这里,初始化的工作就基本完成了,具体的各个组件中初始化代码块和要完成的详细工作,有兴趣的同学可以依次步骤点进去一探究竟

下面的start()过程按照时序图,参照上面的过程我们仍然走一遍流程,最终走到
AbstractProtocol的start()方法,

tomcat启动流程分析
tomcat启动流程分析

tomcat启动流程分析

protected final void startAcceptorThreads() {
        int count = getAcceptorThreadCount();
        acceptors = new Acceptor[count];

        for (int i = 0; i < count; i++) {
            acceptors[i] = createAcceptor();
            String threadName = getName() + "-Acceptor-" + i;
            acceptors[i].setThreadName(threadName);
            Thread t = new Thread(acceptors[i], threadName);
            t.setPriority(getAcceptorThreadPriority());
            t.setDaemon(getDaemon());
            t.start();
        }
    }

来到这里,其实很容易看出来,Acceptor属于一个线程类,继续进入,最终来到NioEndPoint的Acceptor方法中,

protected class Acceptor extends AbstractEndpoint.Acceptor {

        @Override
        public void run() {

            int errorDelay = 0;

            // Loop until we receive a shutdown command
            while (running) {

                // Loop if endpoint is paused
                while (paused && running) {
                    state = AcceptorState.PAUSED;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }

                if (!running) {
                    break;
                }
                state = AcceptorState.RUNNING;

                try {
                    //if we have reached max connections, wait
                    countUpOrAwaitConnection();

                    SocketChannel socket = null;
                    try {
                        // Accept the next incoming connection from the server
                        // socket
                        socket = serverSock.accept();
                    } catch (IOException ioe) {
                        // We didn't get a socket
                        countDownConnection();
                        if (running) {
                            // Introduce delay if necessary
                            errorDelay = handleExceptionWithDelay(errorDelay);
                            // re-throw
                            throw ioe;
                        } else {
                            break;
                        }
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // Configure the socket
                    if (running && !paused) {
                        // setSocketOptions() will hand the socket off to
                        // an appropriate processor if successful
                        if (!setSocketOptions(socket)) {
                            closeSocket(socket);
                        }
                    } else {
                        closeSocket(socket);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("endpoint.accept.fail"), t);
                }
            }
            state = AcceptorState.ENDED;
        }

在run方法中我们注意到下面的这句,翻译过来,就是等待请求的连接,tomcat启动流程分析

总结一下,到这里tomcat通过初始化和start的过程,完成加载容器依赖的各个组件的初始化,并启动IO线程,等待外部请求的接入,下一步,就是具体的请求过来之后如何进行处理的过程,在后续的文章中会继续提到,本篇到此结束,最后感谢观看!