聊聊Dubbo(八):核心原始碼-容器啟動/停止

猿碼道發表於2019-03-04

1 介紹

服務容器是 一個 standalone 的啟動程式,因為後臺服務不需要 Tomcat 或 JBoss 等 Web 容器的功能,如果硬要用 Web 容器去載入服務提供方,增加複雜性,也浪費資源。

服務容器 只是一個簡單的 Main 方法,並載入一個簡單的 Spring 容器,用於暴露服務。

服務容器的載入內容可以擴充套件,內建了 spring, jetty, log4j, logback等載入,可通過容器擴充套件點進行擴充套件。配置配在 java 命令的 -Ddubbo.container 引數或者 dubbo.properties 中。

2 容器型別

2.1 Spring Container

  1. 自動載入 META-INF/spring 目錄下的所有 Spring 配置。
  2. 配置 spring 配置載入位置(配在java命令-D引數或者dubbo.properties中):
    dubbo.container=log4j,spring
    dubbo.spring.config=classpath*:META-INF/spring/*.xml
    複製程式碼

2.2 Jetty Container

  1. 啟動一個內嵌 Jetty,用於彙報狀態。
  2. 配置:
    dubbo.jetty.port=8080:配置 jetty 啟動埠
    dubbo.jetty.directory=/foo/bar:配置可通過 jetty 直接訪問的目錄,用於存放靜態檔案
    dubbo.jetty.page=log,status,system:配置顯示的頁面,預設載入所有頁面
    複製程式碼

2.3 Log4j Container

  1. 自動配置 log4j 的配置,在多程式啟動時,自動給日誌檔案按程式分目錄。
  2. 配置:
    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();
        }
    }

}
複製程式碼
Container SPI 擴充套件配置
  1. 如上圖,依據Dubbo SPI機制,通過ExtensionLoader.getExtensionLoader(Container.class),獲取ExtensionLoader例項:

    private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
    複製程式碼
  2. 通過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]));
    }
    複製程式碼
  3. 遍歷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
複製程式碼

相關文章