• 【一起学源码-微服务】Nexflix Eureka 源码二:EurekaServer启动之配置文件加载以及面向接口的配置项读取


    前言

    上篇文章已经介绍了 为何要读netflix eureka源码了,这里就不再概述,下面开始正式源码解读的内容。

    如若转载 请标明来源:一枝花算不算浪漫

    代码总览

    还记得上文中,我们通过web.xml找到了eureka server入口的类EurekaBootStrap,这里我们就先来简单地看下:

    /**
     * The class that kick starts the eureka server. 负责启动Eureka server的类
     *
     * <p>
     * 这里要注意两个关键点:
     * eureka server对应的配置类为:EurekaServerConfig
     * eureka client对应的配置类为:EurekaInstanceConfig
     *
     * The eureka server is configured by using the configuration
     * {@link EurekaServerConfig} specified by <em>eureka.server.props</em> in the
     * classpath.  The eureka client component is also initialized by using the
     * configuration {@link EurekaInstanceConfig} specified by
     * <em>eureka.client.props</em>. If the server runs in the AWS cloud, the eureka
     * server binds it to the elastic ip as specified.
     * </p>
     *
     * @author Karthik Ranganathan, Greg Kim, David Liu
     *
     * 负责EurekaServer初始化的类
     */
    public class EurekaBootStrap implements ServletContextListener {
    	/**
         * Initializes Eureka, including syncing up with other Eureka peers and publishing the registry.
         *
         * @see
         * javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
         */
        @Override
        public void contextInitialized(ServletContextEvent event) {
            try {
                initEurekaEnvironment();
                initEurekaServerContext();
    
                ServletContext sc = event.getServletContext();
                sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
            } catch (Throwable e) {
                logger.error("Cannot bootstrap eureka server :", e);
                throw new RuntimeException("Cannot bootstrap eureka server :", e);
            }
        }
    }
    

    看下注释 我们可以了解到几个关键点:

    1. eureka server对应的配置类为:EurekaServerConfig
    2. eureka client对应的配置类为:EurekaInstanceConfig
    3. EurekaBootStrap implements ServletContextListener, 所以这里会直接执行contextInitialized方法。

    Eureka-Server 环境配置

    初始化enviroment

    接着近一步往下跟,这里可以先看 initEurekaEnvironment()

    代码如下:

    protected void initEurekaEnvironment() throws Exception {
            logger.info("Setting the eureka configuration..");
    
            // 获取dataCenter数据中心 这里重点看ConfigurationManager
    		// ConfigurationManager:配置管理器,管理eureka自己所有的配置,
    		// 重点:getConfigInstance里面使用的是volatile+synchronized+double check模式的单例模式
    		/**
    		 * ConfigurationManager 创建过程:(继续往后跟读代码)
    		 * 1、创建一个ConcurrentCompositeConfiguration实例,这个类代表了所谓的配置,包括eureka需要的所有配置。
    		 * 2、往ConcurrentCompositeConfiguration加入一堆config,然后返回ConfigurationManager实例
    		 * 3、初始化数据中心的配置,如果没有配置的话就是default data center
    		 * 4、初始化eureka 运行的环境,如果没有配置的话,默认就是test环境
    		 */
            String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
            // 初始化数据中心,没有配置的话 使用DEFAULT data center
            if (dataCenter == null) {
                logger.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
                ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
            } else {
                ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
            }
            // 获取eureka server运行环境,没有配置的话默认使用test环境
    		// 后面读取配置文件会根据运行环境读取,比如eureka-server-test.properties
            String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
            if (environment == null) {
                ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
                logger.info("Eureka environment value eureka.environment is not set, defaulting to test");
            }
        }
    

    这里注释写的比较详细,且这里有两个重点:

    1. getConfigInstance里面使用的是volatile+synchronized+double check模式的单例模式
    2. ConfigurationManager 创建过程

    getConfigInstance

    这里一个关键点 是使用了很经典的double check 单例模式。

    这种单例是一种线程安全的方式,里面使用了volatile+synchronized+double check,具体秒在何处 我这里就不展开讲解了,搜索double check单例模式就会有很多解析文章,这里直接看代码。

    static volatile AbstractConfiguration instance = null;
    
    /**
     * Get the current system wide configuration. If there has not been set, it will return a default
     * {@link ConcurrentCompositeConfiguration} which contains a SystemConfiguration from Apache Commons
     * Configuration and a {@link DynamicURLConfiguration}.
     */
    public static AbstractConfiguration getConfigInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
                }
            }
        }
        return instance;
    }
    

    这里instance用volatile修饰来保证线程之间的可见性和有序性(禁止指令重排序),一般的对象创建过程都是非原子性的,内部会发生指令重排序的情况,所以加上volatile可以防止指令重排。用synchronized来保证线程串行化,double check来保证不被单例化。

    接着我们就继续往下跟,看看ConfigurationManager的创建过程。

    ConfigurationManager 创建

    private static AbstractConfiguration getConfigInstance(boolean defaultConfigDisabled) {
        if (instance == null && !defaultConfigDisabled) {
            instance = createDefaultConfigInstance();
            registerConfigBean();
        }
        return instance;        
    }
    
    private static AbstractConfiguration createDefaultConfigInstance() {
        ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();  
        try {
            DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
            config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
        } catch (Throwable e) {
            logger.warn("Failed to create default dynamic configuration", e);
        }
        if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
            SystemConfiguration sysConfig = new SystemConfiguration();
            config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
        }
        if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
            EnvironmentConfiguration envConfig = new EnvironmentConfiguration();
            config.addConfiguration(envConfig, ENV_CONFIG_NAME);
        }
        ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
        config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
        config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));
        return config;
    }
    
    public ConcurrentCompositeConfiguration()
    {
        clear();
    }
    
    public final void clear()
    {
        fireEvent(EVENT_CLEAR, null, null, true);
        configList.clear();
        namedConfigurations.clear();
        // recreate the in memory configuration
        containerConfiguration = new ConcurrentMapConfiguration();
        containerConfiguration.setThrowExceptionOnMissing(isThrowExceptionOnMissing());
        containerConfiguration.setListDelimiter(getListDelimiter());
        containerConfiguration.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
        containerConfiguration.addConfigurationListener(eventPropagater);
        configList.add(containerConfiguration);
        
        overrideProperties = new ConcurrentMapConfiguration();
        overrideProperties.setThrowExceptionOnMissing(isThrowExceptionOnMissing());
        overrideProperties.setListDelimiter(getListDelimiter());
        overrideProperties.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
        overrideProperties.addConfigurationListener(eventPropagater);
        
        fireEvent(EVENT_CLEAR, null, null, false);
        containerConfigurationChanged = false;
        invalidate();
    }
    

    上面代码是比较多,如果一行行去抠细节 真的就没有必要了,这里我们只是看一些重点的流程,我们上面注释也写到过ConfigurationManager的创建过程:
    1、创建一个ConcurrentCompositeConfiguration实例,这个类代表了所谓的配置,包括eureka需要的所有配置。
    2、往ConcurrentCompositeConfiguration加入一堆config,然后返回ConfigurationManager实例

    这里我是不建议太过于扣细节的,因为往往这些细枝末节的东西会将我们绕进去。

    关于ConfigurationManager具体的细节这里也有两篇比较好的文章推荐:

    1. 关于 Eureka 启动的说明
    2. 微服务动态配置组件netflix archaius

    Eureka-Server 上下文加载

    先看代码:

    protected void initEurekaServerContext() throws Exception {
    	// 1、加载eureka-server properties文件中和配置
        EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
    
        // For backward compatibility
        JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
        XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
    
        logger.info("Initializing the eureka client...");
        logger.info(eurekaServerConfig.getJsonCodecName());
        ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig);
    
        // 2、初始化一个ApplicationInfoManager,和第3步创建eureka client相关,后续会讲解
        ApplicationInfoManager applicationInfoManager = null;
    
    	// 3、初始化eureka-server内部的一个eureka-client(用来跟其他的eureka-server节点做注册和通信)
    	// 类的开头已经说明了:EurekaInstanceConfig其实就是eureka client相关的配置类
        if (eurekaClient == null) {
            EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
                    ? new CloudInstanceConfig()
                    : new MyDataCenterInstanceConfig();
            
            applicationInfoManager = new ApplicationInfoManager(
                    instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
            
            EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
            eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
        } else {
            applicationInfoManager = eurekaClient.getApplicationInfoManager();
        }
    
        // 3、处理注册相关的事情
        PeerAwareInstanceRegistry registry;
        if (isAws(applicationInfoManager.getInfo())) {
            registry = new AwsInstanceRegistry(
                    eurekaServerConfig,
                    eurekaClient.getEurekaClientConfig(),
                    serverCodecs,
                    eurekaClient
            );
            awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
            awsBinder.start();
        } else {
            registry = new PeerAwareInstanceRegistryImpl(
                    eurekaServerConfig,
                    eurekaClient.getEurekaClientConfig(),
                    serverCodecs,
                    eurekaClient
            );
        }
    
        // 4、处理peer节点相关的事情
        PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
                registry,
                eurekaServerConfig,
                eurekaClient.getEurekaClientConfig(),
                serverCodecs,
                applicationInfoManager
        );
    
        // 5、完成eureka-server上下文(context)的构建及初始化
        serverContext = new DefaultEurekaServerContext(
                eurekaServerConfig,
                serverCodecs,
                registry,
                peerEurekaNodes,
                applicationInfoManager
        );
    
        EurekaServerContextHolder.initialize(serverContext);
    
        serverContext.initialize();
        logger.info("Initialized server context");
    
        // Copy registry from neighboring eureka node
    	// 6、处理一些善后的事情,从相邻的eureka节点拷贝注册信息
        int registryCount = registry.syncUp();
        registry.openForTraffic(applicationInfoManager, registryCount);
    
        // Register all monitoring statistics.
    	// 7、注册所有的监控统计项
        EurekaMonitors.registerAllStats();
    }
    

    代码有点长,加载context信息分为了上面注释的好几步,代码注释都有写

    加载eureka-server properties文件中和配置
    EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();

    private static final DynamicStringProperty EUREKA_PROPS_FILE = DynamicPropertyFactory.getInstance().getStringProperty("eureka.server.props","eureka-server");
    
    public DefaultEurekaServerConfig() {
        init();
    }
    
    private void init() {
        String env = ConfigurationManager.getConfigInstance().getString(
                EUREKA_ENVIRONMENT, TEST);
        ConfigurationManager.getConfigInstance().setProperty(
                ARCHAIUS_DEPLOYMENT_ENVIRONMENT, env);
    
        String eurekaPropsFile = EUREKA_PROPS_FILE.get();
        try {
            // ConfigurationManager
            // .loadPropertiesFromResources(eurekaPropsFile);
            ConfigurationManager
                    .loadCascadedPropertiesFromResources(eurekaPropsFile);
        } catch (IOException e) {
            logger.warn(
                    "Cannot find the properties specified : {}. This may be okay if there are other environment "
                            + "specific properties or the configuration is installed with a different mechanism.",
                    eurekaPropsFile);
        }
    }
    
    public static void loadCascadedPropertiesFromResources(String configName) throws IOException {
        Properties props = loadCascadedProperties(configName);
        if (instance instanceof AggregatedConfiguration) {
            ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
            config.loadProperties(props);
            ((AggregatedConfiguration) instance).addConfiguration(config, configName);
        } else {
            ConfigurationUtils.loadProperties(props, instance);
        }
    }
    

    首先我们看下EurekaServerConfig
    image.png

    里面包含好多getxxx方法,看一下具体实现:
    image.png

    其中configInstance是DynamicPropertyFactory对象。EurekaServerConfig,这是个接口,这里面有一堆getXXX()的方法,包含了eureka server需要使用的所有的配置,都可以通过这个接口来获取。

    想象一下,eureka-sever.properties文件里,都是一个一个的key=value的很多的配置项,肯定是将这些key-value格式的配置项加载到内存的Properties对象去存放,Map。一般来说,如果让我们自己来设计这个读取properties文件的配置的代码,也许我们就是做到将配置加载到Properties对象中就结束了。

    EurekaServerConfig,代表了eureka-server需要的所有的配置项,通过接口定义了大量的方法,让你可以从这里获取所有你需要的配置

    DefaultEurekaServerConfig就是上面EurekaServerConfig的实现类,创建实例的时候,会执行一个init()方法,在这个方法中,就会完成eureka-server.properties文件中的配置项的加载。EUREKA_PROPS_FILE,对应着要加载的eureka的配置文件的名字。

    将加载出来的Properties中的配置项都放到ConfigurationManager中去,由这个ConfigurationManager来管理

    比如说eureka-server那个工程里,就有一个src/main/resources/eureka-server.properties文件,只不过里面是空的,全部都用了默认的配置

    DefaultEurekaServerConfig.init()方法中,会将eureka-server.properties文件中的配置加载出来,都放到ConfdigurationManager中去,然后在DefaultEurekaServerConfig的各种获取配置项的方法中,配置项的名字是在各个方硬编码的,是从一个DynamicPropertyFactory里面去获取的,你可以认为DynamicPropertyFactory是从ConfigurationManager那儿来的,因为ConfigurationManager中都包含了加载出来的配置了,所以DynamicPropertyFactory里,也可以获取到所有的配置项

    在从DynamicPropertyFactory中获取配置项的时候,如果你没配置,那么就用默认值,全部都给你弄好了各个配置项的默认值,相当于所有的配置项的默认值,在DefaultEurekaServerConfig的各个方法中,都可以看到,如果你没配置,那么就用这里的默认值就可以了

    加载eureka-server.properties的过程:

    (1)创建了一个DefaultEurekaServerConfig对象
    (2)创建DefaultEurekaServerConfig对象的时候,在里面会有一个init方法
    (3)先是将eureka-server.properties中的配置加载到了一个Properties对象中,然后将Properties对象中的配置放到ConfigurationManager中去,此时ConfigurationManager中去就有了所有的配置了
    (4)然后DefaultEurekaServerConfig提供的获取配置项的各个方法,都是通过硬编码的配置项名称,从DynamicPropertyFactory中获取配置项的值,DynamicPropertyFactory是从ConfigurationManager那儿来的,所以也包含了所有配置项的值
    (5)在获取配置项的时候,如果没有配置,那么就会有默认的值,全部属性都是有默认值的

    申明

    本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

    感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

    22.jpg

  • 相关阅读:
    Findbugs初探-使用idea获取findbugs插件
    idea 14运行java工程报错-Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match.
    Linux定时任务
    模块和包
    流程控制&&函数
    Python 变量与数据类型
    使用代码上传文件示例
    好用的代码示例
    JedisCluster API 整理
    springboot实现转发和重定向
  • 原文地址:https://www.cnblogs.com/wang-meng/p/12089911.html
Copyright © 2020-2023  润新知