Dubbo服务提供者Provider启动流程上(三)

Dubbo在配置的提供上,提供了丰富的功能,也考虑了很多场景,具体可以参考官方文档。其中配置的来源也有很多,包括了外部的配置中心,这些配置是如何获取,如何解析,最后如何在dubbo中生效的呢?带着这些疑问,学习一下dubbo provider启动流程。这边还是第一章以API方式入手,入口在serviceConfg#export。

serviceConfig的继承关系如下:

 

export方法是里面主要是checkAndUpdateSubConfigs和doExport方法。前者实现了外部化配置的初始化过程,以及多个配置优先级问题。后者负责检查和填充配置,最后暴露服务。

本文主要侧重于checkAndUpdateSubConfigs的讲解,服务暴露在下一个章节介绍。

 

checkAndUpdateSubConfigs

public void checkAndUpdateSubConfigs() {
        // Use default configs defined explicitly on global configs
        // 配置优先级 provider  > module > application
        completeCompoundConfigs();
        // Config Center should always being started first.
        startConfigCenter();
        // 实例化 ProviderConfig过程,如果 ProviderConfig 不存在,那么就新建一个默认的,并执行 refresh 方法。
        checkDefault();
        checkApplication();
        checkRegistry();
        checkProtocol();
        // 设置Serviceconfig的变量
        this.refresh();
        //用于设置 MetadataReportConfig,尝试从配置中加载其配置,就算 MetadataReportConfig 不可用(即 unvalid),也只会报警告。
        checkMetadataReport();

        if (StringUtils.isEmpty(interfaceName)) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }

        // 对是否为 GenericService 进行适配(GenricService:泛化调用 是一种 Dubbo 提供的特殊角色,不需要引入jar包,
        // 直接通过GenericService进行调用,用于服务测试以及API服务网关,后面文章细分析)
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, methods);
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }

        // 检查Stub是否实现了interfaceClass 和 Mock属性,从而实现不同逻辑调用
        checkStubAndLocal(interfaceClass);
        checkMock(interfaceClass);
    }

1)completeCompoundConfigs() 这个方法就是定义了取配置的优先级, provider > module > application 也就是说provider的配置存在了 则不会使用module的配置,module的配置存在了则不适用application的配置。

2)startConfigCenter这是配置中心的初始化,这里面如果是API的方式启动dubbo,则配置在serviceconfig中,如果时候spring的方式 则都会初始化在ConfigManager中。

void startConfigCenter() {
        if (configCenter == null) {
            // 如果配置了外部的配置中心  则赋值this.configCenter
            ConfigManager.getInstance().getConfigCenter().ifPresent(cc -> this.configCenter = cc);
        }

        if (this.configCenter != null) {
            // TODO there may have duplicate refresh
            this.configCenter.refresh();
            //这个方法主要就是读取配置中心的配置
            /**
             * 通过SPI机制不同类型配置中心配置
             * 读取配置数据存到到Environment的 externalConfigurationMap 和 appExternalConfigurationMap
             */
            prepareEnvironment();
        }
        // 这边会统一刷新
        ConfigManager.getInstance().refreshAll();
    }

这边先说一下结论,下文会详细介绍。如果配置了configservice配置中心,那么会根据url通过dubbo的SPI来获取对应的配置中心实现,读取的配置信息存放在Environment的externalConfigurationMap和appExternalConfigurationMap中。配置优先级的策略是    SystemConfiguration -> AppExternalConfiguration ->ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration。

如果没有配置configservice配置中心,同时配置了zookeeper注册中心,那么会将zookeeper作为配置中心,并且zookeeper上的配置不会覆盖本地配置,因为AbstractConfig位于AppExternalConfiguration之前,优先级如下SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration

如果没有配置中心,也没有ZK的注册中心,那就默认会使用一个NopDynamicConfiguration作为外部配置中心实现,其内部啥都不处理。

this.configCenter.refresh()和 prepareEnvironment();将在下文介绍。

 

3)  checkDefault -> createProviderIfAbsent  如果provider不存在,则new一个。

4)checkApplication 与 3)类似,如果application不存在则new一个,同时重新设置系统属性dubbo.service.shutdown.wait.seconds

5)checkRegistry:dubbo是支持多个注册中心的,并存储在List<RegistryConfig>。loadRegistriesFromBackwardConfig方法,如果没有配置register,则会从系统 dubbo.registry.address属性中获取或者找到配置文件路径并加载。convertRegistryIdsToRegistries方法表示,先读取外部化配置的注册配置,如果有,则对比本地配置好的注册信息,将外部的注册信息添加进来,前提是本地的注册信息个数不能超过外部的注册个数。如果没有外部配置,且本地有没有配置,则new。重点看useRegistryForConfigIfNecessary方法

private void useRegistryForConfigIfNecessary() {
        registries.stream().filter(RegistryConfig::isZookeeperProtocol).findFirst().ifPresent(rc -> {
            // we use the loading status of DynamicConfiguration to decide whether ConfigCenter has been initiated.
            // 如果存在zk的注册中心,则DynamicConfiguration没有的情况下,将zookeeper作为外部配置中心
            Environment.getInstance().getDynamicConfiguration().orElseGet(() -> {
                ConfigManager configManager = ConfigManager.getInstance();
                ConfigCenterConfig cc = configManager.getConfigCenter().orElse(new ConfigCenterConfig());
                cc.setProtocol(rc.getProtocol());
                cc.setAddress(rc.getAddress());
                /**
                 * 这里有一个细节,在将 registry 变为默认的 ConfigCenter 时候,将优先级设为false,这样就不会让这个配置中心去覆盖本地的配置。
                 */
                cc.setHighestPriority(false);
                setConfigCenter(cc);
                //  // 从配置中心刷新所有配置
                startConfigCenter();
                return null;
            });
        });
    }

接下来回到第二步2),  这个时候 this.configCenter != null返回true,执行refresh和prepareEnvironment。

refresh方法是抽象基类abstractConfig里面,用于该配置类根据Environment中的混合类compositeConfiguration配置来更新自身的配置数据,

public void refresh() {
        try {
            // 获取多个混合配置,包括SystemConfiguration   AppExternalConfiguration ExternalConfiguration PropertiesConfiguration
            CompositeConfiguration compositeConfiguration = Environment.getInstance().getConfiguration(getPrefix(), getId());
            // 自己在程序中,或者是在yml或者xml等配置文件中配置。
            InmemoryConfiguration config = new InmemoryConfiguration(getPrefix(), getId());
            // 将当前类的 信息放入到配置中   实际上是调用get方法来获取
            config.addProperties(getMetaData());
            // 看 isConfig的顺序,从而加载不同配置  外部配置优先
            if (Environment.getInstance().isConfigCenterFirst()) {
                // The sequence would be: SystemConfiguration ->  AppExternalConfiguration ->ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
                compositeConfiguration.addConfiguration(3, config);
            } else {   //这个是外部配置不会覆盖本地配置
                // The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
                compositeConfiguration.addConfiguration(1, config);
            }

            // loop methods, get override value and set the new value back to method
            // 循环,从compositeConfiguration中填充参数到当前配置类
            Method[] methods = getClass().getMethods();
            for (Method method : methods) {
                if (ClassHelper.isSetter(method)) {
                    try {
                        // 在混合配置中循环遍历获取配合,直到获取到,因为这边是本地配置优先,所以ZK的配置不会覆盖掉本地
                        String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
                        // isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
                        if (StringUtils.isNotEmpty(value) && ClassHelper.isTypeMatch(method.getParameterTypes()[0], value)) {
                            method.invoke(this, ClassHelper.convertPrimitive(method.getParameterTypes()[0], value));
                        }
                    } catch (NoSuchMethodException e) {
                        logger.info("Failed to override the property " + method.getName() + " in " +
                                this.getClass().getSimpleName() +
                                ", please make sure every property has getter/setter method provided.");
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Failed to override ", e);
        }
    }

isConfigCenterFirst() = false正是上面cc.setHighestPriority(false);

接下来执行prepareEnvironment,这个过程就是去外部配置拿数据,然后赋值externalConfigurationMap和appExternalConfigurationMap

    private void prepareEnvironment() {
        if (configCenter.isValid()) {
            if (!configCenter.checkOrUpdateInited()) {
                return;
            }
            // //这一步会使用SPI加载DynamicConfigurationFactory实现类,并根据URL中传入的protocol信息来选择具体的实现类,
            // 如果是ZK作为注册中心的话   那么会把zk也作为配置中心
            DynamicConfiguration dynamicConfiguration = getDynamicConfiguration(configCenter.toUrl());
            // //查看配置中信息中dubbo.properties这个文件的配置地址
            String configContent = dynamicConfiguration.getConfig(configCenter.getConfigFile(), configCenter.getGroup());

            String appGroup = application != null ? application.getName() : null;
            String appConfigContent = null;
            // 从配置中心获取 appConfigFile 配置
            if (StringUtils.isNotEmpty(appGroup)) {
                appConfigContent = dynamicConfiguration.getConfig
                        (StringUtils.isNotEmpty(configCenter.getAppConfigFile()) ? configCenter.getAppConfigFile() : configCenter.getConfigFile(),
                         appGroup
                        );
            }
            try {
                // 将获取到的String 类型的 url类型 数据转化为 key value
                Environment.getInstance().setConfigCenterFirst(configCenter.isHighestPriority());
                Environment.getInstance().updateExternalConfigurationMap(parseProperties(configContent));
                Environment.getInstance().updateAppExternalConfigurationMap(parseProperties(appConfigContent));
            } catch (IOException e) {
                throw new IllegalStateException("Failed to parse configurations from Config Center.", e);
            }
        }
    }

6)checkProtocol   协议检查

    private void checkProtocol() {
        // 如果协议protocal没有配置  那就使用provider配置中的协议配置
        if (CollectionUtils.isEmpty(protocols) && provider != null) {
            setProtocols(provider.getProtocols());
        }
        // 这个与5)中convertRegistryIdsToRegistries类似  都是根据外部配置的protocal做检查
        convertProtocolIdsToProtocols();
    }

7)接下来做的事情如下,主要是检查泛化接口,local接口,stub接口,mock配置等。

       // 设置Serviceconfig的变量
        this.refresh();
        //用于设置 MetadataReportConfig,尝试从配置中加载其配置,就算 MetadataReportConfig 不可用(即 unvalid),也只会报警告。
        checkMetadataReport();

        if (StringUtils.isEmpty(interfaceName)) {
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }

        // 对是否为 GenericService 进行适配(GenricService:泛化调用 是一种 Dubbo 提供的特殊角色,不需要引入jar包,
        // 直接通过GenericService进行调用,用于服务测试以及API服务网关,后面文章细分析)
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {
                // 类加载接口
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 比较接口中的方法是否都在methodConfig中
            checkInterfaceAndMethods(interfaceClass, methods);
            // 比较实现类是否集成当前接口
            checkRef();
            // 不是泛化接口
            generic = Boolean.FALSE.toString();
        }
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if (stub != null) {
            if ("true".equals(stub)) {
                // stub默认是接口名字上加上Stub
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // stub实现类是否implementation了interfaceClass
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }

        // 检查Stub是否实现了interfaceClass
        checkStubAndLocal(interfaceClass);
        //检查Mock配置,从而实现不同逻辑调用
        checkMock(interfaceClass);
    }

至此checkAndUpdateSubConfigs这一步讲完了,下一节就是如何暴露服务了。

 

 

 

posted @ 2021-12-18 20:29  gaojy  阅读(507)  评论(0编辑  收藏  举报