1 介绍
服务容器是 一个 standalone 的启动程序,因为后台服务不需要 Tomcat 或 JBoss 等 Web 容器的功能,如果硬要用 Web 容器去加载服务提供方,增加复杂性,也浪费资源。
服务容器 只是一个简单的 Main 方法,并加载一个简单的 Spring 容器,用于暴露服务。
服务容器的加载内容可以扩展,内置了 spring, jetty, log4j, logback等加载,可通过容器扩展点进行扩展。配置配在 java 命令的 -Ddubbo.container
参数或者 dubbo.properties
中。
2 容器类型
2.1 Spring Container
- 自动加载 META-INF/spring 目录下的所有 Spring 配置。
- 配置 spring 配置加载位置(配在java命令-D参数或者dubbo.properties中):
dubbo.container=log4j,spring dubbo.spring.config=classpath*:META-INF/spring/*.xml
2.2 Jetty Container
- 启动一个内嵌 Jetty,用于汇报状态。
- 配置:
dubbo.jetty.port=8080:配置 jetty 启动端口 dubbo.jetty.directory=/foo/bar:配置可通过 jetty 直接访问的目录,用于存放静态文件 dubbo.jetty.page=log,status,system:配置显示的页面,缺省加载所有页面
2.3 Log4j Container
- 自动配置 log4j 的配置,在多进程启动时,自动给日志文件按进程分目录。
- 配置:
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) { } } } } }
-
如上图,依据Dubbo SPI机制,通过
ExtensionLoader.getExtensionLoader(Container.class)
,获取ExtensionLoader实例:private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
-
通过
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])); }
-
遍历
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