tomcat启动流程分析

前言

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

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

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

tomcat 的IO模型
在这里插入图片描述

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

reactor模型

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

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

在这里插入图片描述

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在启动过程中,也是按照server.xml中各个组件标签的顺序依次进行初始化的(各个组件的作用见上一篇)

在这里插入图片描述
其大概的步骤如下:

  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入手,
在这里插入图片描述

我们找到main方法
在这里插入图片描述

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

  • init():初始化组件
  • start():启动组件
  • stop():停止组件
  • destroy():销毁组件
    在这里插入图片描述
    在这里插入图片描述

我们不妨随意找一个组件,像connector来说,其顶级接口还是Lifecycle
在这里插入图片描述

各组件的默认实现

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

Server
在这里插入图片描述

Service
在这里插入图片描述

Engine
在这里插入图片描述
Host
在这里插入图片描述
Context
在这里插入图片描述

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

源码启动入口

Bootstrap ->main()

在这里插入图片描述
从main方法入手,可以分为2步,第一步init()的过程,即初始化上述时序图中各个组件的过程

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

在这里插入图片描述

调起catalina的getServer().init()方法
在这里插入图片描述

在这里插入图片描述
调起server的init()
在这里插入图片描述

在initInternal的init方法中,可以看到像globalNamingResources这样的方法初始化,其实就是在依次解析server.xml中的标签
在这里插入图片描述

来到本方法最后一段,看到调用了service的init()方法
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

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

跳过中间的engine,host和context,我们最后直接来到connector的初始化
在这里插入图片描述
调起AbstractProtocol的init()方法
在这里插入图片描述
最终来到endpoint的init()
在这里插入图片描述

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

如果继续点进bind方法,可以看到其实就是在创建并绑定相应的连接信息
在这里插入图片描述

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

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

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

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

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小码农叔叔

谢谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值