ZStack启动流程

这篇文章我们重点来看看zstack启动的过程中做了什么,如何启动起来的。github: https://github.com/SnailJie/ZstackWithComments.git

我们知道,zstack的组成中,核心是MN(ManagementNode)。Dashboard只是作为面向用户操作的命令发起者。命令发出后还是由MN进行消息分发处理。因此我们主要关注点在于如何MN是如何启动的。Here we go。


MN部分是基于Java SpringMVC进行的研发。因此启动的时候,会首先读取conf/web.xml。可以看看这个代码:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    metadata-complete="true">

    <absolute-ordering />

    <servlet>
        <servlet-name>ZStack Dispatcher Servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/classes/zstack-servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/zstack-servlet-context.xml</param-value>
    </context-param>

    <context-param>
        <param-name>parentContextKey</param-name>
        <param-value>parentContext</param-value>
    </context-param>

    <servlet-mapping>
        <servlet-name>ZStack Dispatcher Servlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/static/*</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>UrlRewriteFilter</filter-name>
        <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/static/pypi/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    <!-- NOTE: this listener must be put as the first listener to execute, don't change the order !!! BootstrapWebListener里面只有一条调用的方法,目测是想利用直接调用来使得类加载、初始化-->
    <listener>
        <listener-class>
            org.zstack.portal.managementnode.BootstrapWebListener
        </listener-class>
    </listener>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener 
        </listener-class>
    </listener>


<!-- 在这里开始启动Management节点。想看启动过程都做了哪些事情?到ComponentLoaderWebListener里面看看吧 。在web容器启动时,会自动加载这个类的初始化方法-->
    <listener>
        <listener-class>
            org.zstack.portal.managementnode.ComponentLoaderWebListener
        </listener-class>
    </listener>

</web-app>

可以看到,web容器在启动时,会自动去加载ComponentLoaderWebListener,ComponentLoaderWebListener实现了ServletContextListener的两个接口,因此在启动时,会自动去调用ComponentLoaderWebListener里面的contextInitialized方法。这里我们进行看一下具体的代码

ComponentLoaderWebListener.java
*RenJie:
 * 这里开始进行初始化操作。可以看到基本是创建并启动ManagementNode,同时创建云总线。想看
 * ManagementNode 启动过程还是想了解云总线?
*/    

    @Override
    public void contextInitialized(ServletContextEvent event) {
        try {
            if (!isInit) {
                Platform.createComponentLoaderFromWebApplicationContext(WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()));
                node = Platform.getComponentLoader().getComponent(ManagementNodeManager.class);
                bus = Platform.getComponentLoader().getComponent(CloudBus.class);    //创建云总线,可以看看怎么创建的
                node.startNode();      //启动管理节点。具体细节我们进ManagementNodeManagerImpl里面看看
                isInit = true;
            }
        } catch (Throwable t) {
            logger.warn("failed to start management server", t);
            // have to call bus.stop() because its init has been called by spring
            if (bus != null) {
                bus.stop();
            }

            Throwable root = ExceptionDSL.getRootThrowable(t);
            new BootErrorLog().write(root.getMessage());
            if (CoreGlobalProperty.EXIT_JVM_ON_BOOT_FAILURE) {
                System.exit(1);
            } else {
                throw new CloudRuntimeException(t);
            }
        }
    }

可以看到,在这里,主要操作的是两个部分,一个是cloudbus,一个是managementnode。cloudbus是整个后台核心的消息传输通道,可以理解,首先得把“路”修好,然后才能方便地进行后面的其他操作。因此首先是初始化cloudbus了,然后再初始化MN。OK,那我们就先看cloudbus的初始化,然后看看MN。

CloudBus的初始化

CloudBus的初始化并不在ManagementNode的启动流程中,他单独先启了起来,下面的这行代码的实现中,就初始化了CloudBus。

Platform.createComponentLoaderFromWebApplicationContext(WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()));

进一步查看createComponentLoaderFromWebApplicationContext()的实现方式,有这么一行

bus = loader.getComponentNoExceptionWhenNotExisting(CloudBus.class);

如果进一步进去看,其实这行的代码的实现是利用BeanFactory,采用IOC的机制,去获取CloudBus的Bean。可以看一下CloudBus的spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     ……
    default-init-method="init" default-destroy-method="destroy">

 ……
 ……

    <bean id="CloudBus" class = "org.zstack.core.cloudbus.CloudBusImpl2" depends-on="ThreadFacade,ThreadAspectj">
        <zstack:plugin>
            <zstack:extension interface="org.zstack.header.managementnode.ManagementNodeChangeListener" order="9999"/>
        </zstack:plugin>
    </bean>


</beans>

可以看到具体的CloudBus实现是CloudBusImpl2,默认的初始化方法是init。因此我们去看看CloudBusImpl2的init方法。

//CloudBus的初始化方法,在加载的时候自动调用init方法。CloudBus的具体实现是利用RabbitMQ的实现,因此CloudBus的初始化也主要是对RabbitMQ的配置
    void init() {
        trackerClose = CloudBusGlobalProperty.CLOSE_TRACKER;
        serverIps = CloudBusGlobalProperty.SERVER_IPS;
        tracker = new MessageTracker();

        ConnectionFactory connFactory = new ConnectionFactory();     //ConnectionFactory用于配置RabbitMQ与broker链接的配置信息
        List<Address> addresses = CollectionUtils.transformToList(serverIps, new Function<Address, String>() {
            @Override
            public Address call(String arg) {
                return Address.parseAddress(arg);
            }
        });
        connFactory.setAutomaticRecoveryEnabled(true);
        connFactory.setRequestedHeartbeat(CloudBusGlobalProperty.RABBITMQ_HEART_BEAT_TIMEOUT);
        connFactory.setNetworkRecoveryInterval((int) TimeUnit.SECONDS.toMillis(CloudBusGlobalProperty.RABBITMQ_NETWORK_RECOVER_INTERVAL));
        connFactory.setConnectionTimeout((int) TimeUnit.SECONDS.toMillis(CloudBusGlobalProperty.RABBITMQ_CONNECTION_TIMEOUT));

        logger.info(String.format("use RabbitMQ server IPs: %s", serverIps));

        try {
            if (CloudBusGlobalProperty.RABBITMQ_USERNAME != null) {
                connFactory.setUsername(CloudBusGlobalProperty.RABBITMQ_USERNAME);
                logger.info(String.format("use RabbitMQ username: %s", CloudBusGlobalProperty.RABBITMQ_USERNAME));
            }
            if (CloudBusGlobalProperty.RABBITMQ_PASSWORD != null) {
                connFactory.setPassword(CloudBusGlobalProperty.RABBITMQ_PASSWORD);
                logger.info("use RabbitMQ password: ******");
            }
            if (CloudBusGlobalProperty.RABBITMQ_VIRTUAL_HOST != null) {
                connFactory.setVirtualHost(CloudBusGlobalProperty.RABBITMQ_VIRTUAL_HOST);
                logger.info(String.format("use RabbitMQ virtual host: %s", CloudBusGlobalProperty.RABBITMQ_VIRTUAL_HOST));
            }

            conn = connFactory.newConnection(addresses.toArray(new Address[]{}));    //创建与RabbitMQ的链接
            logger.debug(String.format("rabbitmq connection is established on %s", conn.getAddress()));

            ((Recoverable)conn).addRecoveryListener(new RecoveryListener() {
                @Override
                public void handleRecovery(Recoverable recoverable) {
                    logger.info(String.format("rabbitmq connection is recovering on %s", conn.getAddress().toString()));
                }
            });

            channelPool = new ChannelPool(CloudBusGlobalProperty.CHANNEL_POOL_SIZE, conn);   //创建了100个通道的连接池
            createExchanges();    //在一个channel上建立了三种路由,
            outboundQueue = new BusQueue(makeMessageQueueName(SERVICE_ID), BusExchange.P2P);
            Channel chan = channelPool.acquire();
            chan.queueDeclare(outboundQueue.getName(), false, false, true, queueArguments());
            chan.basicConsume(outboundQueue.getName(), true, consumer);
            chan.queueBind(outboundQueue.getName(), outboundQueue.getBusExchange().toString(), outboundQueue.getBindingKey());
            channelPool.returnChannel(chan);
            //在这儿之下创建了几个队列,具体是用来干啥的现在还不知道
            maid.construct();     
            noRouteEndPoint.construct();
            tracker.construct();
            tracker.trackService(SERVICE_ID);
        } catch (Exception e) {
            throw new CloudRuntimeException(e);
        }
    }

这部分就是具体的初始化代码了。CloudBus的具体实现是利用RabbitMQ的实现,因此CloudBus的初始化也主要是对RabbitMQ的配置,以及创建连接池、创建路由以及几个队列等。

ManagemenNode的初始化

ManagementNode的初始化,就是定义了一系列的操作来初始化对象。可以ManagementNodeManagerImpl里的start方法查看到启动的流程。这里就不贴代码了,太长了。

Managementnode启动过程中,会做以下步骤:

1.初始化云总线
云总线的初始化已经在前面做过了,因此这步直接被跳过
2.启动各种组件
里面的Component,只要实现了Component接口的,都会被加载进入容器,并且被调用Component.start启动组件

ManagementNodeManagerImpl.java
then(new NoRollbackFlow() {
                String __name__ = "populate-components";     //RenJie: 启动各种组件【哪些组件?】

                @Override
                public void run(FlowTrigger trigger, Map data) {
                    populateComponents();    //这个里面的实现
                    trigger.next();
                }

3.把管理节点作为微服务中的服务加入他的消息总线
zstack的进程内微服务理念。管理节点也是众多服务中的一个,需要注册到消息总线上去。
4.把刚才的注册的组件都启动起来
5.初始化数据库
6.把管理节点的信息持久化到数据库中
7.开启心跳监测
8.把API组件注册到消息总线上去
9.开启监控管理节点的生命状态、发布管理节点已经就绪的信息

启动过程中,我觉得比较重要的是一个是消息总线(已经在前面初始化过了),一个是API组件。
消息总线起来了,那么整套核心部分就可以进行消息传递,进行微服务传递。而API组件是进行消息的注册以及转发的。因此也非常重要。

至此呢,MN也成功初始化并且启动起来了。接着就可以接受来自dashboard进行的请求了

猜你喜欢

转载自blog.csdn.net/Snail_Ren/article/details/77879100
今日推荐