eureka-server 启动的源码剖析(1)
eureka-server 启动的源码剖析(2)

eureka-server 的 web 工程结构分析及 web.xml 解读 这篇中,我们找到了 eureka-server 作为 web 应用启动的入口 Listener: EurekaBootStrap,现在通过阅读源码来了解其中的流程和技术亮点。

1. 初始化代码的入口 EurekaBootStrap

  • EurekaBootStap 实现了 ServletContextListener 接口,那么 Servlet 容器会在初始化 ServletContext 时,先调用 contextInitialized() 方法;
  • 在这个方法中会去初始化环境变量、初始化 eureka server 上下文; ```java package com.netflix.eureka;

public class EurekaBootStrap implements ServletContextListener { … @Override public void contextInitialized(ServletContextEvent event) { try { initEurekaEnvironment(); initEurekaServerContext(); … } … } }

  1. <a name="m5A2h"></a>
  2. # 2. 环境初始化 & 基于单例模式的 ConfigurationManager
  3. <a name="hGvwN"></a>
  4. ## 2.1 环境初始化的主要过程
  5. - 应用会通过调用 initEurekaEnvironment(),初始化 eureka-server 的环境;
  6. - 在这个方法中会调用 ConfigurationManager.getConfigInstance() 方法,初始化配置管理器 ConfigurationManager 的实例(单例模式,double check+volatile);
  7. - 初始化数据中心 DataCenter 的配置,默认是 default;
  8. - 初始化 eureka Environment,默认是 test 环境;
  9. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/1471554/1652947617938-d7953cd3-3c2f-4160-9f7e-9731b5eeefb5.png#clientId=u10aa16b9-1b21-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ua1036728&margin=%5Bobject%20Object%5D&name=image.png&originHeight=331&originWidth=363&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17989&status=done&style=none&taskId=ua40f75b9-200f-49a8-b56b-855da68b0c3&title=)
  10. ```java
  11. protected void initEurekaEnvironment() throws Exception {
  12. ...
  13. String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
  14. if (dataCenter == null) {
  15. ...
  16. ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
  17. } else {
  18. ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
  19. }
  20. String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
  21. if (environment == null) {
  22. ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
  23. ...
  24. }
  25. }

2.2 ConfigurationManager 单例初始化的过程

  • Eureka Server 内大量使用了 Netflix Archaius 项目(配置增强库)中的 ConfigurationManager,所以这个组件被设计成了单例;
  • 通过 double check + volatile 的方式实现配置管理器的单例化;
  • 创建一个 ConcurrentCompositeConfiguration 实例,往实例中添加一些 config,然后返回这个实例作为配置管理器的实例; ```java package com.netflix.config;

public class ConfigurationManager { static volatile AbstractConfiguration instance = null; … public static AbstractConfiguration getConfigInstance() { if (instance == null) { synchronized (ConfigurationManager.class) { if (instance == null) { instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG)); } } } return instance; } … private static AbstractConfiguration createDefaultConfigInstance() { ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();

    ...
    config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);

    ...
    config.addConfiguration(sysConfig, SYS_CONFIG_NAME);

    ...
    config.addConfiguration(envConfig, ENV_CONFIG_NAME);

    ...
    config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);

    ...
    return config;
}

}

:::info

- Netflix Archaius 项目,是一个配置增强库,不仅提供了读取本地配置文件、环境变量等基础功能,还提供了定时拉取远程配置数据源到本地等高级功能。在这里我们可以简单理解为就是一个读取本地配置的工具类就可以了。[https://github.com/Netflix/archaius](https://www.yuque.com/xiongsanxiansheng/ptb7u9/xgkwad)
- [Double-Check + Volatile 实现单例模式](https://www.yuque.com/xiongsanxiansheng/ptb7u9/xgkwad)
:::
<a name="QKHwq"></a>
# 3. 配置文件加载 & 面向接口的配置项读取
<a name="PE3MV"></a>
## 3.1 加载 eureka-server.properties 配置文件的过程
![eureka server 启动之加载配置文件.png](https://cdn.nlark.com/yuque/0/2022/png/1471554/1652999807282-32159201-ba5b-438e-9998-4439585244e2.png#clientId=ua56ad864-5c3b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=572&id=u384829d2&margin=%5Bobject%20Object%5D&name=eureka%20server%20%E5%90%AF%E5%8A%A8%E4%B9%8B%E5%8A%A0%E8%BD%BD%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6.png&originHeight=1144&originWidth=1256&originalType=binary&ratio=1&rotation=0&showTitle=false&size=96725&status=done&style=none&taskId=u1d929bb4-6a07-4218-80fe-32c67ab0ece&title=&width=628)

- 应用通过调用 initEurekaServerContext(),来初始化 eureka server 上下文;
- 创建一个 DefaultEurekaServerConfig 对象,在创建对象的时候,会调用里面的 init() 方法;
```java
protected void initEurekaServerContext() throws Exception {
    EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();

    ...
}
  • init() 方法中,会去将 eureka-server.properties 文件中的配置项加载出来,都放 ConfigurationManager 中,由这个 ConfigurationManager 来管理;
    • 配置文件名是在 DefaultEurekaServerConfig 中硬编码的,eureka-server 与 .properties 拼接成 eureka-server 的配置文件的名称 ```java private static final DynamicStringProperty EUREKA_PROPS_FILE = DynamicPropertyFactory
         .getInstance().getStringProperty("eureka.server.props", "eureka-server");
      

private void init() { …

String eurekaPropsFile = EUREKA_PROPS_FILE.get();
...
ConfigurationManager.loadCascadedPropertiesFromResources(eurekaPropsFile);
...

}


- 具体加载配置文件的过程:
   - 应用通过 ConfigurationManager.loadCascadedProperties(...) 方法,启动一个线程去加载 eureka-server.properties 文件,将其中的配置加载到 Properties 对象中;
   - 然后会加载 eureka-server-**环境**.properties 中的配置,加载到另一个 Properties 中,覆盖之前那个老的 Properties 中的属性;
   - 最后将所有的配置项 Properties 加载到 ConfigurationManager 单例进行管理;
```java
public static void loadCascadedPropertiesFromResources(String configName) throws IOException {
    Properties props = loadCascadedProperties(configName);
    ...
    ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
    config.loadProperties(props);
    ((AggregatedConfiguration) instance).addConfiguration(config, configName);
    ...
}


private static Properties loadCascadedProperties(String configName) throws IOException {
    String defaultConfigFileName = configName + ".properties";

    ...
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    URL url = loader.getResource(defaultConfigFileName);

    ...
    Properties props = getPropertiesFromFile(url);
    String environment = getDeploymentContext().getDeploymentEnvironment();
    if (environment != null && environment.length() > 0) {
        String envConfigFileName = configName + "-" + environment + ".properties";
        url = loader.getResource(envConfigFileName);
        if (url != null) {
            Properties envProps = getPropertiesFromFile(url);
            if (envProps != null) {
                props.putAll(envProps);
            }
        }
    }
    return props;
}

3.2 面向接口的配置项读取

eureka server 启动之读取配置.png

  • eureka server 通过 ConfigurationManager 从配置文件中加载所有配置项,如果没有会使用默认配置,然后由 ConfigurationManager 管理配置项;
  • eureka server 采用面向接口的方式获取配置,在 EurekaServerConfig 接口中定义了大量获取配置项的方法;
  • 在 DefaultEurekaServerConfig 类中实现了接口中的方法,并且在各个方法中硬编码配置项的名字,通过 DynamicPropertyFactory 实例来读取出对应配置名的配置值;(如果没有配置值,就使用方法中设置的默认值)
  • DynamicPropertyFactory 相当于是基于 ConfigurationManager 做了一层简单的封装,里面也包含了所有配置项;

    public class DefaultEurekaServerConfig implements EurekaServerConfig {
      private static final DynamicPropertyFactory configInstance = com.netflix.config.DynamicPropertyFactory
              .getInstance();
    
      ...
      @Override
      public int getRemoteRegionRegistryFetchInterval() {
          return configInstance.getIntProperty(
                  namespace + "remoteRegion.registryFetchIntervalInSeconds", 30)
                  .get();
      }
      ...
      @Override
      public long getEvictionIntervalTimerInMs() {
          return configInstance.getLongProperty(
                  namespace + "evictionIntervalTimerInMs", (60 * 1000)).get();
      }
      ...
    }
    
    public class DynamicPropertyFactory {
      private static DynamicPropertyFactory instance = new DynamicPropertyFactory();
      ...
    
      private volatile static DynamicPropertySupport config = null;
      ...
    
      public static DynamicPropertyFactory getInstance() {
          if (config == null) {
              synchronized (ConfigurationManager.class) {
                  if (config == null) {
                      AbstractConfiguration configFromManager = ConfigurationManager.getConfigInstance();
                      if (configFromManager != null) {
                          initWithConfigurationSource(configFromManager);
                          initializedWithDefaultConfig = !ConfigurationManager.isConfigurationInstalled();
                          logger.info("DynamicPropertyFactory is initialized with configuration sources: " + configFromManager);
                      }
                  }
              }
          }
          return instance;
      }
      ...
    }
    

    :::info 技术亮点:
    通常获取配置的各种配置项,有两种方式:

  • 使用大量的常量来定义配置名,获取配置属性,Config.get(ConfigKeys.XX_XX_XX),例如 spark;

  • 面向接口,定义大量的获取配置属性的方法,在各个方法中定义配置名,Config.getXxXxXx(),例如 eureka; :::

    总结:eureka-server 启动的部分源码图

    eureka server 启动流程.png