【一起学源码-微服务】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

posted @ 2019-12-24 10:05  一枝花算不算浪漫  阅读(1088)  评论(4编辑  收藏  举报