1. 程式人生 > >dubbo啟動服務之容器(Container)

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