Java高级架构邦仁聊技术后端应用技术

☆聊聊Dubbo(七):核心源码-容器启动/停止

2018-04-20  本文已影响354人  七寸知架构

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
上一篇下一篇

猜你喜欢

热点阅读