这篇文章我们重点来看看zstack启动的过程中做了什么,如何启动起来的。github: https://github.com/SnailJie/ZstackWithComments.git
我们知道,zstack的组成中,核心是MN(ManagementNode)。Dashboard只是作为面向用户操作的命令发起者。命令发出后还是由MN进行消息分发处理。因此我们主要关注点在于如何MN是如何启动的。Here we go。
MN部分是基于Java SpringMVC进行的研发。因此启动的时候,会首先读取conf/web.xml。可以看看这个代码:
<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 Servletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/classes/zstack-servlet-context.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/classes/zstack-servlet-context.xmlparam-value>
context-param>
<context-param>
<param-name>parentContextKeyparam-name>
<param-value>parentContextparam-value>
context-param>
<servlet-mapping>
<servlet-name>ZStack Dispatcher Servletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<servlet-mapping>
<servlet-name>defaultservlet-name>
<url-pattern>/static/*url-pattern>
servlet-mapping>
<filter>
<filter-name>UrlRewriteFilterfilter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilterfilter-class>
filter>
<filter-mapping>
<filter-name>UrlRewriteFilterfilter-name>
<url-pattern>/static/pypi/*url-pattern>
<dispatcher>REQUESTdispatcher>
<dispatcher>FORWARDdispatcher>
filter-mapping>
<listener>
<listener-class>
org.zstack.portal.managementnode.BootstrapWebListener
listener-class>
listener>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
listener-class>
listener>
<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的初始化并不在ManagementNode的启动流程中,他单独先启了起来,下面的这行代码的实现中,就初始化了CloudBus。
Platform.createComponentLoaderFromWebApplicationContext(WebApplicationContextUtils.getWebApplicationContext(event.getServletContext()));
进一步查看createComponentLoaderFromWebApplicationContext()的实现方式,有这么一行
bus = loader.getComponentNoExceptionWhenNotExisting(CloudBus.class);
如果进一步进去看,其实这行的代码的实现是利用BeanFactory,采用IOC的机制,去获取CloudBus的Bean。可以看一下CloudBus的spring配置文件
<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
addresses = CollectionUtils.transformToList(serverIps, new Function() {
@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的配置,以及创建连接池、创建路由以及几个队列等。
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进行的请求了