1 介紹
服務(wù)容器是 一個 standalone 的啟動程序,因?yàn)楹笈_服務(wù)不需要 Tomcat 或 JBoss 等 Web 容器的功能,如果硬要用 Web 容器去加載服務(wù)提供方,增加復(fù)雜性,也浪費(fèi)資源敬辣。
服務(wù)容器 只是一個簡單的 Main 方法雪标,并加載一個簡單的 Spring 容器,用于暴露服務(wù)溉跃。
服務(wù)容器的加載內(nèi)容可以擴(kuò)展村刨,內(nèi)置了 spring, jetty, log4j, logback等加載,可通過容器擴(kuò)展點(diǎn)進(jìn)行擴(kuò)展撰茎。配置配在 java 命令的 -Ddubbo.container
參數(shù)或者 dubbo.properties
中嵌牺。
2 容器類型
2.1 Spring Container
- 自動加載 META-INF/spring 目錄下的所有 Spring 配置。
- 配置 spring 配置加載位置(配在java命令-D參數(shù)或者dubbo.properties中):
dubbo.container=log4j,spring dubbo.spring.config=classpath*:META-INF/spring/*.xml
2.2 Jetty Container
- 啟動一個內(nèi)嵌 Jetty龄糊,用于匯報狀態(tài)逆粹。
- 配置:
dubbo.jetty.port=8080:配置 jetty 啟動端口 dubbo.jetty.directory=/foo/bar:配置可通過 jetty 直接訪問的目錄,用于存放靜態(tài)文件 dubbo.jetty.page=log,status,system:配置顯示的頁面炫惩,缺省加載所有頁面
2.3 Log4j Container
- 自動配置 log4j 的配置僻弹,在多進(jìn)程啟動時,自動給日志文件按進(jìn)程分目錄他嚷。
- 配置:
dubbo.log4j.file=/foo/bar.log:配置日志文件路徑 dubbo.log4j.level=WARN:配置日志級別 dubbo.log4j.subdirectory=20880:配置日志子目錄蹋绽,用于多進(jìn)程啟動,避免沖突
3 容器啟動
com.alibaba.dubbo.container.Main
是服務(wù)啟動的主類筋蓖,缺省只加載 spring:
java com.alibaba.dubbo.container.Main
通過 main 函數(shù)參數(shù)傳入要加載的容器:
java com.alibaba.dubbo.container.Main spring jetty log4j
通過 JVM 啟動參數(shù)傳入要加載的容器:
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;
/**
* 啟動發(fā)布
* @param args
*/
public static void main(String[] args) {
try {
// 開始判斷main函數(shù)的傳入?yún)?shù),在args參數(shù)為空的情況下粘咖,從部署環(huán)境中取得dubbo.container屬性蚣抗,
if (args == null || args.length == 0) {
// 讀取dubbo.properties中dubbo.container屬性值,為空時通過loader.getDefaultExtensionName()獲取默認(rèn)值
String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
args = Constants.COMMA_SPLIT_PATTERN.split(config);
}
final List<Container> containers = new ArrayList<Container>();
// 遍歷獲取指定名稱的擴(kuò)展加入到列表中
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關(guān)閉的鉤子瓮下,用來在jvm關(guān)閉時關(guān)閉容器
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();
}
}
}
});
}
// 啟動服務(wù)
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) {
}
}
}
}
}
-
如上圖翰铡,依據(jù)Dubbo SPI機(jī)制,通過
ExtensionLoader.getExtensionLoader(Container.class)
讽坏,獲取ExtensionLoader實(shí)例:private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
-
通過
loader.getExtension(args[i])
两蟀,獲取擴(kuò)展類實(shí)例: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!"); }
看到這里我們發(fā)現(xiàn)程序一旦啟動就一直在運(yùn)行震缭,但是我們還是沒有到如何加載dubbo spring配置文件,不要著急战虏,我們繼續(xù)看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 優(yōu)雅停機(jī)
Dubbo是通過JDK的 ShutdownHook
來完成優(yōu)雅停機(jī)的拣宰,所以如果用戶使用 kill -9 PID
等強(qiáng)制關(guān)閉指令,是不會執(zhí)行優(yōu)雅停機(jī)的,只有通過 kill PID
時烦感,才會執(zhí)行巡社。
4.1 源碼分析
服務(wù)容器通過Runtime.getRuntime().addShutdownHook(new Thread())
添加停機(jī)時的回調(diào)鉤子,源碼如下:
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 容器擴(kuò)展
服務(wù)容器擴(kuò)展手趣,用于自定義加載內(nèi)容晌该。
5.1 擴(kuò)展示例
Maven 項(xiàng)目結(jié)構(gòu):
src
|-main
|-java
|-com
|-xxx
|-XxxContainer.java (實(shí)現(xiàn)Container接口)
|-resources
|-META-INF
|-dubbo
|-com.alibaba.dubbo.container.Container (純文本文件肥荔,內(nèi)容為: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