Tomcat启动流程

Tomcat启动分析

Tomcat作为独立的Servlet容器启动时,由引导类Bootstrap启动,Bootstrap中定义了main函数,是Tomcat启动的入口。
在启动的流程中(参数为start),main方法主要做了两件事情:

  • 初始化类加载器
  • 加载Catalina类并调用start方法

所以整个启动的流程是从Catalina.start开始的。那么可不可以不要Bootstrap直接用Catalina类来启动呢?也是可以的,不过就需要将tomcat依赖的库都加到classpath中,不够灵活。使用Bootstrap引导,Catalina是通过CatalinaLoader加载的,CatalinaLoader的加载路径由配置文件配置,更加灵活。
Catalina.start:

public void start() {

        if (getServer() == null) {
            load();
        }

        if (getServer() == null) {
            log.fatal(sm.getString("catalina.noServer"));
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            getServer().start();

...以下省略

整个start方法主要也是做了两件事情:

  • 创建server
  • 调用server.start()

start方法执行完成后,就完成了tomcat的启动,但是整个流程还是相当复杂的,要了解整个流程,必须先对tomcat的各个组件和他们相互之间的联系有基本了解,它们构成了整个tomcat的骨架。引用之前学习的文章中的图片:

图片源自https://www.ibm.com/developerworks/cn/java/j-lo-tomcat1

可以看到,服务器最顶层的抽象是server对象,一个server可以包含多个service来提供服务,每个service又包含多个Connector和一个Container,其中Connector负责接受请求,处理连接相关的逻辑,将连接数据转化为Request、Response交由Container处理,Container是Servlet容器,负责寻找目标Servlet来处理请求。Container从上到下又分为Engine、Host、Context、Wrapper,一个service包含一个Engine,表示一个完整的容器引擎,一个Engine又可包含一个或多个Host,每一个表示一个抽象主机,一个Host又包含一个或多个Context,每一个表示一个Webapp, 一个Context又包含一个或多个Wrapper,Wrapper是对具体Servlet的包装。
下面回到server的创建和启动,主要就是围绕上述组建进行的。
server对象的创建
server对象的创建是通过Digester来解析server.xml配置文件完成的。server.xml定义来server的结构,对service、connector、Engine、Host、Context等组件进行了配置。Diggest基于Sax对server.xml进行解析,定义了各个节点的事件规则,解析完成后即完成了整个server对象的创建,实现类为StandardServer。
server的启动
Tomcat的组件都实现了Lifecycle接口来对生命周期进行管理。
抽象类LifecycleBase对生命周期的各种方法进行了实现,定义了公共的抽象流程,并定义了模板方法startInternal又具体的类来实现。
standardServer.startInternal:

...
// Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
...

可以看到server.start的实现主要就是对包含的所有service的start进行调用。这里可以猜测一下,service.start的实现,应该也是对包含的connectors和container进行start,具体源码就不贴了。
connector和container的启动时整个服务器启动的重头戏。
connector的启动
前面讲过connector主要是处理客户端连接请求的,主要功能是接受客户端的tcp请求,封装成Request、Response模型给container处理。下面通过源码来看下它启动的流程。
Connector创建的时候需要传入protocol,以确定支持的协议和使用的io模型,默认值是Http11NioProtocol,也就是http1.1的nio实现。connector.start调用了protocolHandler.start,以Http11NioProtocol为例, Http11NioProtocol创建时创建了NioEndpoint,start也是对NioEndpoint进行启动,由此看出connector的io处理都是委托给对应的Endpoint进行处理的。
NioEndPoint在初始化时,会初始化serverSocket:

protected void initServerSocket() throws Exception {
        if (!getUseInheritedChannel()) {
            serverSock = ServerSocketChannel.open();
            socketProperties.setProperties(serverSock.socket());
            InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
            serverSock.socket().bind(addr,getAcceptCount());
        } else {
            Channel ic = System.inheritedChannel();
            if (ic instanceof ServerSocketChannel) {
                serverSock = (ServerSocketChannel) ic;
            }
            if (serverSock == null) {
                throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
            }
        }
        serverSock.configureBlocking(true); 
    }

创建ServerSocket并绑定设置的端口,此时还未准备接收请求,在启动时才开启线程进行接收的。
NioEndpoint启动的部分代码:

...
// Create worker collection
            if (getExecutor() == null) {
                createExecutor();
            }

            initializeConnectionLatch();

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

            startAcceptorThread();

启动的过程中,会启动三类线程,Executor,Poller,Acceptor。Acceptor线程负责接受客户端的请求,即上面的serverSock.accept操作,连接建立后,会将socket注册到poller线程,等待读写事件触发,Nio就体现在poller线程中,用极少的线程(默认是1个)管理多个连接,一旦poller线程中注册的连接有数据可读,即提交到executor线程池进行处理,这三类线程创建完毕后,connector的创建流程基本就结束了。从这个流程中也对tomcat调优最常用的三个参数有了深入的理解:maxThreads,maxConnections,acceptCount。
maxThreads是创建executor线程池时传入的最大线程数,也就是tomcat最多能同时处理的请求数。
maxConnections是Acceptor线程在accept时判断的连接数,是一个锁,在已建立的连接(包括正在处理的和线程池队列中等待处理的)达到maxConnections时进行阻塞,不再调用accept,此时客户端仍然可以发起连接,还可以请求的连接数由acceptCount决定,acceptCount是传入tcp的backlog,表示在应用层接受连接前,tcp可等待的队列长度,但是这个参数的实现和系统有关,不一定会生效。
container的启动
前面讲过container由一个Engine和它包含的Hosts以及Contexts和Wrappers组成。但是刚创建的server未必都有这些组件,常见的情况是有只有一个Engine和它旗下的一个Host, Host会设置一个appBase目录,在Host启动时对appBase内的war包和目录进行自动部署,每一个项目对应一个Context,然后解析每个项目中的web.xml,将其中定义的Servlet解析出来,创建对于的Wrapper对象。容器类都扩展自ContainerBase,触发生命周期方法时会同时调起子容器对应的生命周期。container的启动由Engine.start触发,依次调用各个子容器的start,完成各自的准备工作。

connector和container都启动完成后,整个服务器的启动也就完成了,下面就可以接受请求进行处理了。

你可能感兴趣的:(Tomcat启动流程)