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,源码如下:

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 volatile boolean running = true;
    /**
     * 启动发布
     * @param args
     */
    public static void main(String[] args) {
        try {
            // 开始判断main函数的传入参数,在args参数为空的情况下,从部署环境中取得dubbo.container属性,
            if (args == null || args.length == 0) {
                // 读取dubbo.properties中dubbo.container属性值,为空时通过loader.getDefaultExtensionName()获取默认值
                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.");

            // 添加jvm关闭的钩子,用来在jvm关闭时关闭容器
            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();
                            }
                        }
                    }
                });
            }

            // 启动服务
            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);
        }
        synchronized (Main.class) {
            while (running) {
                try {
                    Main.class.wait();
                } catch (Throwable e) {
                }
            }
        }
    }
}

 

 
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!");
    }
    

看到这里我们发现程序一旦启动就一直在运行,但是我们还是没有到如何加载dubbo spring配置文件,不要着急,我们继续看start和stop方法:

public class SpringContainer implements Container {
    private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
    public static final String SPRING_CONFIG = "dubbo.spring.config";

    public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
    static ClassPathXmlApplicationContext context;

    public static ClassPathXmlApplicationContext getContext() {
        return context;
    }
    public void start() {
        String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
        if (configPath == null || configPath.length() == 0) {
            configPath = DEFAULT_SPRING_CONFIG;
        }
        context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"));
        context.start();
    }
    public void stop() {
        try {
            if (context != null) {
                context.stop();
                context.close();
                context = null;
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }
}

 

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

 


转载:
作者:猿码道
链接:https://www.jianshu.com/p/dfe23a5abcd0