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
,源码如下:
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 volatile boolean running = true;
/**
* 启动发布
* @param args
*/
public static void main(String[] args) {
try {
// 开始判断main函数的传入参数,在args参数为空的情况下,从部署环境中取得dubbo.container属性,
if (args == null || args.length == 0) {
// 读取dubbo.properties中dubbo.container属性值,为空时通过loader.getDefaultExtensionName()获取默认值
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.");
// 添加jvm关闭的钩子,用来在jvm关闭时关闭容器
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);
}
synchronized (Main.class) {
running = false;
Main.class.notify();
}
}
}
});
}
// 启动服务
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);
}
synchronized (Main.class) {
while (running) {
try {
Main.class.wait();
} catch (Throwable e) {
}
}
}
}
}
-
如上图,依据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!"); }
看到这里我们发现程序一旦启动就一直在运行,但是我们还是没有到如何加载dubbo spring配置文件,不要着急,我们继续看start和stop方法:
public class SpringContainer implements Container {
private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
public static final String SPRING_CONFIG = "dubbo.spring.config";
public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
static ClassPathXmlApplicationContext context;
public static ClassPathXmlApplicationContext getContext() {
return context;
}
public void start() {
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
if (configPath == null || configPath.length() == 0) {
configPath = DEFAULT_SPRING_CONFIG;
}
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
context.start();
}
public void stop() {
try {
if (context != null) {
context.stop();
context.close();
context = null;
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}
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