技术点滴

Tomcat源码学习(三)——启动流程之start

2018-11-06  本文已影响0人  笨小天

上一篇对于load过程进行学习,从Bootstrap的入口类可以看到启动过程主要分为三个流程——init,load和start。之前已经学习了init和load的源码,今天就来一探tomcat启动中最后一个流程start


Tomcat启动Bootstrap调用顺序

之前我们看到bootstarp的start方法实际是通过反射调用了Catalina的start方法,接下来就看一下start方法的内容

 /**
     * Start a new server instance.
     */
    public void start() {
        if (getServer() == null) {
            load();
        }
        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }
        long t1 = System.nanoTime();
        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }
        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }
        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);

            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }
        if (await) {
            await(); //监听关闭服务连接信息端口并阻塞,默认8005
            stop();
        }
    }

首先可以看到,会对server进行npl检测,如果server没有初始化就会调用load方法来进行初始化工作,然后映入眼帘的就是Start方法——今天的正主了;完成start后,会向JVM中添加钩子方法,这里的钩子方法时为了优雅的关闭tomcat。

点到getServer().start()方法,会发现有一次来到了LifecycleBase类,这会不一样的是我们看到的是一个新的模板方法start。让我们来先看看start方法都做了什么

@Override
    public final synchronized void start() throws LifecycleException {

        if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
                LifecycleState.STARTED.equals(state)) {

            if (log.isDebugEnabled()) {
                Exception e = new LifecycleException();
                log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
            } else if (log.isInfoEnabled()) {
                log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
            }

            return;
        }

        if (state.equals(LifecycleState.NEW)) {
            init();
        } else if (state.equals(LifecycleState.FAILED)) {
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }

        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            startInternal();
            if (state.equals(LifecycleState.FAILED)) {
                // This is a 'controlled' failure. The component put itself into the
                // FAILED state so call stop() to complete the clean-up.
                stop();
            } else if (!state.equals(LifecycleState.STARTING)) {
                // Shouldn't be necessary but acts as a check that sub-classes are
                // doing what they are supposed to.
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            } else {
                setStateInternal(LifecycleState.STARTED, null, false);
            }
        } catch (Throwable t) {
            // This is an 'uncontrolled' failure so put the component into the
            // FAILED state and throw an exception.
            handleSubClassException(t, "lifecycleBase.startFail", toString());
        }
    }

看到start的实现,会发现比init方法处理复杂了很多,细看会发现start方法会对当前的LifecycleBase实例的状态进行判断,然后调用startInternal方法,然后根据startInternal的结果,再次更新该实例的状态。相当于一个流程控制。这里又引出了startInternal方法,这个就会在继承了LifecycleBase的实现类中实现了,上文中提到过tomcat默认的server是StandardServer,那么就来看看server的Start到底做了些什么。

 @Override
    protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();

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

从代码汇总可以看到,StandardServr的start过程是先根据Server实例创建生命周期事件实例,再将该事件实例依次注册到配置的listener上。最后一次执行Service的start方法来启动配置的ServerService。在对load过程学习的时候,我们知道默认情况下ServerServIce只有一个——StandardService。

 @Override
    protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }

        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        mapperListener.start();

        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            }
        }
    }

可以看到start过程和load过程是对应的,不过相对于load过程,对于container,executor和connector的start都使用了同步操作。

那么就先来看看Container的启动过程,根据load学习,得知了默认的container是StandardEngine,它的UML关系如下


StandardEngine 继承关系

阅读源码会发现StandardEngine的启动过程,是在其父类ContainerBase中实现的,话不多说先来一波源码

 @Override
    protected synchronized void startInternal() throws LifecycleException {
        // Start our subordinate components, if any
        logger = null;
        getLogger();
        Cluster cluster = getClusterInternal();
        if (cluster instanceof Lifecycle) {
            ((Lifecycle) cluster).start();
        }
        Realm realm = getRealmInternal();
        if (realm instanceof Lifecycle) {
            ((Lifecycle) realm).start();
        }
        // Start our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }
        MultiThrowable multiThrowable = null;
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Throwable e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                if (multiThrowable == null) {
                    multiThrowable = new MultiThrowable();
                }
                multiThrowable.add(e);
            }
        }
        if (multiThrowable != null) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                    multiThrowable.getThrowable());
        }
        // Start the Valves in our pipeline (including the basic), if any
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }
        setState(LifecycleState.STARTING);
        // Start our thread
        threadStart();
    }

在container的启动过程中,会异步的对container的子container进行Start的操作。并通过Future来获取其子container的Start结果,如果其中任何一个子container启动异常,都会添加到multiThrowable实例中,然后抛出异常。最后拉起Container的校验session有效期的线程,同时该线程也是一个守护线程。

接下来对配置的工作线程池进行启动操作,源码中提供的默认server.xml中并没有配置工作线程池,不配置时所有的connector共享一个默认线程池,配置后connector可以使用专属的线程池,给8080端口配置使用名称为tomcatThreadPool的线程池,方法如下

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
<Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

访问8080端口时,将会使用tomcatThraeadPool这个线程池中的线程进行处理,而不是默认线程池。最后进行connector的启动。到这里tomcat完成了整个启动过程,可以对外提供服务了。

上一篇下一篇

猜你喜欢

热点阅读