Tree's Blog

Home Projects AboutRSS

Dubbo 启停原理解析

本文抄自《深入理解 Apache Dubbo 和实战》。但相关代码部分会更详细,读者也可以自行 fork 源码阅读。本文 Dubbo 源码版本为 2.7.5,个别地方会和原版书中描述有出入。

本章详细探讨 Dubbo 配置的设计模型、服务暴露的原理、服务消费的原理和优雅停机的原理。

一、配置解析

目前 Dubbo 框架同时提供 3 种配置方式:XML 配置、注解、属性文件(properties 和 yml)配置,最常用的还是 XML 和注解的方式。Dubbo 2.5.6 后重写了注解的逻辑,解决了一些遗留bug,更好地支持 dubbo-spring-boot。本文详细探讨 schema 设计、XML 解析和注解配置实现原理。

1.1 基于 schema 设计解析

Dubbo 框架也直接集成了 Spring 的能力,利用 Spring 配置文件扩展出自定义的解析方式。Dubbo 配置约束文件在 dubbo-config/dubbo-config-spring\src\main\resources\dubbo.xsd 中。

xsd 文件用来约束使用 XML 配置时的标签和对应的属性,如 <dubbo:service>标签等。Spring 在解析到自定义的 namespace 标签时,会查找对应的 spring.schemas 和 spring.handlers 文件,最终触发 Dubbo 的 DubboNameSpaceHandler 类来进行初始化和解析。

目前绝大多数场景使用默认的 Dubbo 配置就足够了。

1.2 基于 XML 配置原理解析

配置解析入口在 DubboNamespaceHandler:

public class DubboNamespaceHandler extends NamespaceHandlerSupport implements ConfigurableSourceBeanMetadataElement {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
        registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
        registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

    /**
     * Override {@link NamespaceHandlerSupport#parse(Element, ParserContext)} method
     *
     * @param element       {@link Element}
     * @param parserContext {@link ParserContext}
     * @return
     * @since 2.7.5
     */
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        BeanDefinitionRegistry registry = parserContext.getRegistry();
        registerAnnotationConfigProcessors(registry);
        registerApplicationListeners(registry);
        BeanDefinition beanDefinition = super.parse(element, parserContext);
        setSource(beanDefinition);
        return beanDefinition;
    }

    /**
     * Register {@link ApplicationListener ApplicationListeners} as a Spring Bean
     *
     * @param registry {@link BeanDefinitionRegistry}
     * @see ApplicationListener
     * @see AnnotatedBeanDefinitionRegistryUtils#registerBeans(BeanDefinitionRegistry, Class[])
     * @since 2.7.5
     */
    private void registerApplicationListeners(BeanDefinitionRegistry registry) {
        registerBeans(registry, DubboLifecycleComponentApplicationListener.class);
        registerBeans(registry, DubboBootstrapApplicationListener.class);
    }

    /**
     * Register the processors for the Spring Annotation-Driven features
     *
     * @param registry {@link BeanDefinitionRegistry}
     * @see AnnotationConfigUtils
     * @since 2.7.5
     */
    private void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(registry);
    }
}

该类用于把不同的标签关联到解析实现类中,registerBeanDefinitionParser 方法是 Spring 的方法,传入两个参数,表明在 Dubbo 遇到第一个参数为标签名的时候,委托给 DubboBeanDefinitionParser ,在其构造器中传入了标签对应的 Config 类。

源码点击查看:DubboBeanDefinitionParser

Dubbo 只是对 XML 配置标签做了属性的提取,运行时的属性注入和转换都是 Spring 处理的,要了解 Spring 如何做数据初始化和转换,参见 Spring 的 BeanWrapperImpl。

DubboBeanDefinitionParser 的 parse 方法解析标签最终得到 BeanDefinition,最终还是委托 Spring 创建 Java 对象。

1.3 基于注解配置原理解析

注解处理逻辑包含 3 部分内容,第一部分是如果用户使用了配置文件,则框架按需生成对应 Bean,第二部分是要将所有使用 Dubbo 的注解 @Service 的 class 变为 bean,第三部分是 @Reference 注解的字段或方法注入代理对象。

主要涉及的类有:EnableDubbo,激活注解;DubboConfigConfigurationRegistrar ,配置读取;ServiceAnnotationBeanPostProcessor,提升 @Service 注解的服务为 Spring Bean;ReferenceAnnotationBeanPostProcessor,注入 @Reference 引用。

@EnableDubboConfig
@DubboComponentScan
@EnableDubboLifecycle
public @interface EnableDubbo {
    ...
}

@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {
    ...
}

@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
    ...
}

Spring 容器启动时,如果注解了 @EnableDubbo,则会自动 Import 注入 DubboConfigConfigurationRegistrar 和 DubboComponentScanRegistrar,DubboLifecycleComponentRegistrar。

public class DubboConfigConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                importingClassMetadata.getAnnotationAttributes(EnableDubboConfig.class.getName()));

        boolean multiple = attributes.getBoolean("multiple");
        // 单一配置绑定
        // Single Config Bindings
        registerBeans(registry, DubboConfigConfiguration.Single.class);
        // 外部化配置绑定 see https://mercyblitz.github.io/2018/01/18/Dubbo-%E5%A4%96%E9%83%A8%E5%8C%96%E9%85%8D%E7%BD%AE/
        if (multiple) { // Since 2.6.6 https://github.com/apache/dubbo/issues/3193
            registerBeans(registry, DubboConfigConfiguration.Multiple.class);
        }

        // Register DubboConfigAliasPostProcessor
        registerDubboConfigAliasPostProcessor(registry);

        // Register NamePropertyDefaultValueDubboConfigBeanCustomizer
        registerDubboConfigBeanCustomizers(registry);

    }

    private void registerDubboConfigBeanCustomizers(BeanDefinitionRegistry registry) {
        registerInfrastructureBean(registry, BEAN_NAME, NamePropertyDefaultValueDubboConfigBeanCustomizer.class);
    }

    /**
     * Register {@link DubboConfigAliasPostProcessor}
     *
     * @param registry {@link BeanDefinitionRegistry}
     * @since 2.7.4 [Feature] https://github.com/apache/dubbo/issues/5093
     */
    private void registerDubboConfigAliasPostProcessor(BeanDefinitionRegistry registry) {
        registerInfrastructureBean(registry, DubboConfigAliasPostProcessor.BEAN_NAME, DubboConfigAliasPostProcessor.class);
    }

}

只看单一配置绑定,可以看到注册了 DubboConfigConfiguration.Single.class,

@EnableConfigurationBeanBindings({
            @EnableConfigurationBeanBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.module", type = ModuleConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.config-center", type = ConfigCenterBean.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.metadata-report", type = MetadataReportConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.metrics", type = MetricsConfig.class),
            @EnableConfigurationBeanBinding(prefix = "dubbo.ssl", type = SslConfig.class)
    })
    public static class Single {

    }

这里又出现了新的注解 @EnableConfigurationBeanBindings,@EnableConfigurationBeanBinding,这两个注解分别自动 import ConfigurationBeanBindingsRegister 和 ConfigurationBeanBindingRegister。

public class ConfigurationBeanBindingsRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private ConfigurableEnvironment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AnnotationAttributes attributes = AnnotationAttributes.fromMap(
                importingClassMetadata.getAnnotationAttributes(EnableConfigurationBeanBindings.class.getName()));
		// 获取 Bindings 注解里的所有值,即所有 @EnableConfigurationBeanBinding
        AnnotationAttributes[] annotationAttributes = attributes.getAnnotationArray("value");
		// 这里直接 new 了一个 ConfigurationBeanBindingRegistrar
        ConfigurationBeanBindingRegistrar registrar = new ConfigurationBeanBindingRegistrar();

        registrar.setEnvironment(environment);
		// 把所有 EnableConfigurationBeanBinding 注解包含的 Bean 注册到 Spring 容器
        for (AnnotationAttributes element : annotationAttributes) {
            registrar.registerConfigurationBeanDefinitions(element, registry);
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        this.environment = (ConfigurableEnvironment) environment;
    }
}

ConfigurationBeanBindingRegistrar 代码不具体分析了,总的来说就是创建 BeanDefinition 到容器,并配置 ConfigurationBeanBindingPostProcessor 委托 Spring 做属性绑定。

以上都是 Dubbo 配置类的 Bean,接下来看 @Service 和 @Reference 是如何注册为 Bean 的。之前说过,@EnableDubbo 也会启动 @DubboComponentScan,然后就会自动 Import 类 DubboComponentScanRegistrar:

public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);

        registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);

        registerReferenceAnnotationBeanPostProcessor(registry);

    }
    ...
}

我们发现这里注册了 ServiceAnnotationBeanPostProcessor 和 ReferenceAnnotationBeanPostProcessor。根据名字很容易理解这是处理 @Service 和 @Reference 注解的。

首先看 Service 的注入:

源码参考:ServiceAnnotationBeanPostProcessor.class

总结一下:通过包扫描提升@Service 注解的类为 Bean,然后生成 ServiceBean 定义,最后还是要生成 Spring 的 RootBeanDefinition,用于 Spring 启动后的服务暴露。

接下来继续看消费方的 @Reference 注解。前面分析过是注册 ReferenceAnnotationBeanPostProcessor 实现的,主要做了几种事情:

  1. 获取标注 @Reference 注解的字段和方法
  2. 反射设置字段或方法对应的引用。

该类继承 AbstractAnnotationBeanPostProcessor,Spring 里用于自定义注解注入的工具类,构造器为指定的注解,这里就是 @Reference 注解,包括 apache 包下和 alibaba 包下的(为了兼容)。

源码参考:ReferenceAnnotationBeanPostProcessor.class

总结:Reference 类的注册的核心类为 ReferenceBean,这是一个 Dubbo 自实现的 Spring FactoryBean。首先会查找 Spring 本地有没有要引用的 ServiceBean,如果有的话,就直接以此 bean 生成代理对象实例。如果找不到,说明是远程的 ServiceBean。则调用 ReferenceBean 的 get 方法获取远程 Service 的代理对象实例。

二、远程服务的暴露机制

前面主要探讨了 Dubbo 中 schema、XML 和 注解相关原理,这些内容对理解框架整体至关重要,且上一部分和 Spring 联系紧密,如果对 Spring 源码熟悉的话,读起来应该是得心应手的(博主是不太行)。在此基础上我们继续探讨服务是如何依靠前面的配置进行服务暴露的。

2.1 配置承载初始化

不管在服务暴露还是在服务消费场景,Dubbo 框架都会根据优先级对配置信息做聚合处理,目前默认覆盖策略主要遵循以下几点规则:

  1. -D 传递给 JVM 参数的优先级最高,如 -Ddubbo.protocol.port=20880。
  2. 代码或 XML 配置优先级次高,如 Spring XML 文件 指定端口号。
  3. 配置文件优先级最低,如 dubbo.properties 文件指定 dubbo.protocol.port=20880

一般推荐配置文件作为默认值。

Dubbo 的配置也会受到 provider 的影响,属于运行时属性值影响,同样遵循以下规则:

  1. 如果只有 provider 端指定配置,则会自动透传到客户端(如 timeout)
  2. 如果客户端也配置了相应属性,则服务端配置会被覆盖(如 timeout)

运行时属性随着框架特性可以动态添加,因此覆盖策略中包含的属性没办法全列出来,一般不允许透传的属性都会在 ClusterUtils#mergeUrl

2.2 远程服务的暴露机制

在详细探讨服务暴露细节前,先看一下整体 RPC 的暴露原理,如图。

image-20200306232802972