专栏首页一枝花算不算浪漫的专栏【一起学源码-微服务】Nexflix Eureka 源码二:EurekaServer启动之配置文件加载以及面向接口的配置项读取

【一起学源码-微服务】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修饰来保证线程之间的可见性,用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

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

其中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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【一起学源码-微服务】Ribbon 源码三:Ribbon与Eureka整合原理分析

    上一篇讲了Ribbon的初始化过程,从LoadBalancerAutoConfiguration 到RibbonAutoConfiguration 再到Ribb...

    一枝花算不算浪漫
  • 业务需求:数据库如何保证先查询后插入/更新 原子性?

    当操作积分用户表时,如果accountId在表中没有数据,那么我们新增一条数据,设置用户积分。如果accountId在表中有数据,我们需要更新用户积分。

    一枝花算不算浪漫
  • [Spring框架]Spring 事务管理基础入门总结.

    一枝花算不算浪漫
  • java IO流文件的读写具体实例

    关于java IO流的操作是非常常见的,基本上每个项目都会用到,每次遇到都是去网上找一找就行了,屡试不爽。上次突然一个同事问了我java文件的读取,我一下子就懵...

    bear_fish
  • 5年内不得晋升!中科院“木兰”换皮Python涉事人被处分:涉及虚假欺瞒,岗位等级削减一级

    1月23日,中国科学院计算技术研究所,官网发布关于“木兰”语言问题的调查与处理意见。

    量子位
  • 机器学习——KNN邻近算法

    1、KNN 算法概述 Cover和Hart在1968年提出了最初的邻近算法。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来...

    week
  • 【React】383- React Fiber:深入理解 React reconciliation 算法

    React 是一个用于构建用户交互界面的 JavaScript 库,其核心机制就是跟踪组件的状态变化,并将更新的状态映射到到新的界面。在 React 中,我们将...

    pingan8787
  • Internationalization(i18n) support in SAP CRM,UI5 and Hybris

    i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称。对程序来说,在不修改内部代码的情况...

    Jerry Wang
  • 一文牢记HTTP状态码(图解HTTP状态码)

    HTTP状态码负责表示客户端HTTP请求的返回结果、标记服务器的处理是否正常、通知出现的错误等工作。

    海盗船长
  • Redis实现参数的集中式管理【面试+工作】

    分布式缓存Redis也提供了类似的发布订阅功能,并且Redis本身提供了缓存和持久化的功能,本文将介绍通过Redis实现简单的参数集中式管理。

    Java帮帮

扫码关注云+社区

领取腾讯云代金券