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();
}
}
}
複製程式碼
-
如上圖,依據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
複製程式碼