☆聊聊Dubbo(七):核心源码-容器启动/停止
1 介绍
服务容器是 一个 standalone 的启动程序,因为后台服务不需要 Tomcat 或 JBoss 等 Web 容器的功能,如果硬要用 Web 容器去加载服务提供方,增加复杂性,也浪费资源。
服务容器 只是一个简单的 Main 方法,并加载一个简单的 Spring 容器,用于暴露服务。
服务容器的加载内容可以扩展,内置了 spring, jetty, log4j, logback等加载,可通过容器扩展点进行扩展。配置配在 java 命令的 -Ddubbo.container
参数或者 dubbo.properties
中。
2 容器类型
2.1 Spring Container
- 自动加载 META-INF/spring 目录下的所有 Spring 配置。
- 配置 spring 配置加载位置(配在java命令-D参数或者dubbo.properties中):
dubbo.container=log4j,spring dubbo.spring.config=classpath*:META-INF/spring/*.xml
2.2 Jetty Container
- 启动一个内嵌 Jetty,用于汇报状态。
- 配置:
dubbo.jetty.port=8080:配置 jetty 启动端口 dubbo.jetty.directory=/foo/bar:配置可通过 jetty 直接访问的目录,用于存放静态文件 dubbo.jetty.page=log,status,system:配置显示的页面,缺省加载所有页面
2.3 Log4j Container
- 自动配置 log4j 的配置,在多进程启动时,自动给日志文件按进程分目录。
- 配置:
dubbo.log4j.file=/foo/bar.log:配置日志文件路径 dubbo.log4j.level=WARN:配置日志级别 dubbo.log4j.subdirectory=20880:配置日志子目录,用于多进程启动,避免冲突
3 容器启动
com.alibaba.dubbo.container.Main
是服务启动的主类,缺省只加载 spring:
java com.alibaba.dubbo.container.Main
通过 main 函数参数传入要加载的容器:
java com.alibaba.dubbo.container.Main spring jetty log4j
通过 JVM 启动参数传入要加载的容器:
java com.alibaba.dubbo.container.Main -Ddubbo.container=spring,jetty,log4j
通过 classpath 下的 dubbo.properties 配置传入要加载的容器:
dubbo.container=spring,jetty,log4j
3.1 源码分析
com.alibaba.dubbo.container.Main
,源码如下:
package com.alibaba.dubbo.container;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.ConfigUtils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Main. (API, Static, ThreadSafe)
*/
public class Main {
public static final String CONTAINER_KEY = "dubbo.container";
public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
private static final ReentrantLock LOCK = new ReentrantLock();
private static final Condition STOP = LOCK.newCondition();
public static void main(String[] args) {
try {
if (args == null || args.length == 0) {
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
final List<Container> containers = new ArrayList<Container>();
for (int i = 0; i < args.length; i++) {
containers.add(loader.getExtension(args[i]));
}
logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
for (Container container : containers) {
try {
container.stop();
logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
LOCK.lock();
STOP.signal();
} finally {
LOCK.unlock();
}
}
}
});
}
for (Container container : containers) {
container.start();
logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}
System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
} catch (RuntimeException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
System.exit(1);
}
try {
LOCK.lock();
STOP.await();
} catch (InterruptedException e) {
logger.warn("Dubbo service server stopped, interrupted by other thread!", e);
} finally {
LOCK.unlock();
}
}
}
Container SPI 扩展配置
-
如上图,依据Dubbo SPI机制,通过
ExtensionLoader.getExtensionLoader(Container.class)
,获取ExtensionLoader实例:private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
-
通过
loader.getExtension(args[i])
,获取扩展类实例:final List<Container> containers = new ArrayList<Container>(); for (int i = 0; i < args.length; i++) { containers.add(loader.getExtension(args[i])); }
-
遍历
containers
,启动容器:for (Container container : containers) { container.start(); logger.info("Dubbo " + container.getClass().getSimpleName() + " started!"); }
4 优雅停机
Dubbo是通过JDK的 ShutdownHook
来完成优雅停机的,所以如果用户使用 kill -9 PID
等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID
时,才会执行。
4.1 源码分析
服务容器通过Runtime.getRuntime().addShutdownHook(new Thread())
添加停机时的回调钩子,源码如下:
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
for (Container container : containers) {
try {
container.stop();
logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
LOCK.lock();
STOP.signal();
} finally {
LOCK.unlock();
}
}
}
});
}
5 容器扩展
服务容器扩展,用于自定义加载内容。
5.1 扩展示例
Maven 项目结构:
src
|-main
|-java
|-com
|-xxx
|-XxxContainer.java (实现Container接口)
|-resources
|-META-INF
|-dubbo
|-com.alibaba.dubbo.container.Container (纯文本文件,内容为:xxx=com.xxx.XxxContainer)
XxxContainer.java:
package com.xxx;
import com.alibaba.dubbo.container.Container;
public class XxxContainer implements Container {
public Status start() {
// ...
}
public Status stop() {
// ...
}
}
META-INF/dubbo/com.alibaba.dubbo.container.Container:
xxx=com.xxx.XxxContainer