美文网首页
Tomcat启动流程

Tomcat启动流程

作者: 狒狒_94d7 | 来源:发表于2020-03-17 21:46 被阅读0次

    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启动流程

          本文链接:https://www.haomeiwen.com/subject/ezwtyhtx.html