【一起學源碼-微服務】Nexflix Eureka 源碼二:EurekaServer啟動之配置文件載入以及面向介面的配置項讀取

  • 2019 年 12 月 25 日
  • 筆記

前言

上篇文章已經介紹了 為何要讀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