dubbo啟動服務之容器(Container)
from http://www.ccblog.cn/75.htm
講解dubbo啟動服務的時候先來了解下java的spi機制
一:SPI 簡介
SPI 全稱為 (Service Provider Interface) ,是JDK內建的一種服務提供發現機制。 目前有不少框架用它來做服務的擴充套件發現, 簡單來說,它就是一種動態替換髮現的機制, 舉個例子來說, 有個介面,想執行時動態的給它新增實現,你只需要新增一個實現,而後,把新加的實現,描述給JDK知道就行啦(通過改一個文字檔案即可) ,公司內部目前Dubbo框架就基於SPI機制提供擴充套件功能。
程式碼例子
public interface Cmand { public void execute(); } public class ShutdownCommand implements Cmand { public void execute() { System.out.println("shutdown...."); } } public class StartCommand implements Cmand { public void execute() { System.out.println("start...."); } } public class SPIMain { public static void main(String[] args) { ServiceLoader<Cmand> loader = ServiceLoader.load(Cmand.class); System.out.println(loader); for (Cmand Cmand : loader) { Cmand.execute(); } } }
配置:
com.unei.serviceloader.impl.ShutdownCommand
com.unei.serviceloader.impl.StartCommand
執行結果:
java.util.ServiceLoader[com.unei.serviceloader.Cmand]
shutdown....
start....
相關原理解答
1.配置檔案為什麼要放在META-INF/services下面?
ServiceLoader類定義如下:
private static final String PREFIX = "META-INF/services/"; (JDK已經寫死了)
但是如果ServiceLoader在load時提供Classloader,則可以從其他的目錄讀取。
2.ServiceLoader讀取實現類是什麼時候例項化的?
public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); } private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
二:dubbo服務啟動之Container
Dubbo的總體架構如下圖所示:
1:dubbo幾大角色
Provider: 暴露服務的服務提供方。
Consumer: 呼叫遠端服務的服務消費方。
Registry: 服務註冊與發現的註冊中心。
Monitor: 統計服務的呼叫次調和呼叫時間的監控中心。
Container: 服務執行容器。
2:Container詳解
Dubbo的Container詳解模組,是一個獨立的容器,因為服務通常不需要Tomcat/JBoss等Web容器的特性,沒必要用Web容器去載入服務。
服務容器只是一個簡單的Main方法,並載入一個簡單的Spring容器,用於暴露服務。
com.alibaba.dubbo.container.Main 是服務啟動的主類
通過圖可以瞭解Container介面有隻有 start() stop()他的實現類
他的實現類有SpringContainer、Log4jContainer、JettyContainer、JavaConfigContainer、LogbackContainer。
當然你也可以自定義容器
官網:http://dubbo.io/Developer+Guide.htm#DeveloperGuide-ContainerSPI
Spring Container
自動載入META-INF/spring目錄下的所有Spring配置。 (這個在原始碼裡面已經寫死了DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml" 所以服務端的主配置放到META-INF/spring/這個目錄下)
配置:(配在java命令-D引數或者dubbo.properties中)
dubbo.spring.config=classpath*:META-INF/spring/*.xml ----配置spring配置載入位置Container
Jetty Container
啟動一個內嵌Jetty,用於彙報狀態。
配置:(配在java命令-D引數或者dubbo.properties中)
dubbo.jetty.port=8080 ----配置jetty啟動埠
dubbo.jetty.directory=/foo/bar ----配置可通過jetty直接訪問的目錄,用於存放靜態檔案
dubbo.jetty.page=log,status,system ----配置顯示的頁面,預設載入所有頁面
Log4j Container
自動配置log4j的配置,在多程序啟動時,自動給日誌檔案按程序分目錄。
配置:(配在java命令-D引數或者dubbo.properties中)
dubbo.log4j.file=/foo/bar.log ----配置日誌檔案路徑
dubbo.log4j.level=WARN ----配置日誌級別
dubbo.log4j.subdirectory=20880 ----配置日誌子目錄,用於多程序啟動,避免衝突
3:容器啟動
從上面的我們知道只需執行main方法就能啟動服務,那麼預設到底是呼叫那個容器呢?
檢視com.alibaba.dubbo.container.Container介面我們發現他上面有一個註解@SPI("spring")
通過上面對SPI的瞭解我們猜想他應該有對應的檔案 如圖:
通過上圖可以知道預設呼叫的是com.alibaba.dubbo.container.spring.SpringContainer
這裡main方法裡面dubbo他們自定義了一個loader,叫ExtensionLoader
所以目前啟動容器的時候我們可以選:spring、javaconfig、jetty、log4j、logback等引數
4:容器停止
Dubbo是通過JDK的ShutdownHook來完成優雅停機的,所以如果使用者使用"kill -9 PID"等強制關閉指令,是不會執行優雅停機的,只有通過"kill PID"時,才會執行。
停止原始碼(在Main方法裡面):
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();
}
}
}
});
}
原理:
服務提供方
停止時,先標記為不接收新請求,新請求過來時直接報錯,讓客戶端重試其它機器。
然後,檢測執行緒池中的執行緒是否正在執行,如果有,等待所有執行緒執行完成,除非超時,則強制關閉。
服務消費方
停止時,不再發起新的呼叫請求,所有新的呼叫在客戶端即報錯。
然後,檢測有沒有請求的響應還沒有返回,等待響應返回,除非超時,則強制關閉。
5:服務jar打包
我這裡用到了maven打,在pom檔案裡面新增以下內容
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib</classpathPrefix>
<mainClass>com.alibaba.dubbo.container.Main</mainClass>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>pre-package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.demo.dubbox.service</groupId>
<artifactId>demo_order_api</artifactId>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWrite>true</overWrite>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<targetPath>${project.build.directory}/classes</targetPath>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<targetPath>${project.build.directory}/classes/META-INF/spring</targetPath>
<directory>src/main/resources/</directory>
<filtering>true</filtering>
<!-- 這個是主配置,把他打包到META-INF/spring/ 其他的xml都在spring-all.xml裡面包含了 當然名字自定義-->
<includes>
<include>spring-all.xml</include>
</includes>
</resource>
</resources>
</build>
6:簡單啟動指令碼
#!/bin/sh JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home JAVA_OPTS="-Xms256m -Xmx512m" java -jar XXX.jar > "log.log" 2>&1