使用Netflix Archaius進行配置管理

在這篇文章中,我們將討論Archaius,一個非常酷且易於使用的Netflix配置管理工具。

通常我們都是如何讀取配置變量的呢?

一種是使用System.getProperty()方法獲得JVM系統屬性。例如下面這樣:

String prop = System.getProperty("myProperty");
int x = DEFAULT_VALUE;
try {
  x = Integer.parseInt(prop);
} catch (NumberFormatException e) {
  // handle format issues
}
myMethod(x);

您還可以使用Spring讀取屬性文件 。或者你的數據庫中有一個簡單的鍵/值表,你可以從那裏讀取一些屬性。又或者您從外部REST端點獲取它們。除此之外還可以從其他類型的鍵/值存儲中獲取,比如Redis或Memcached。

無論情況如何,您的配置變量可能來自許多不同的來源,特別是如果您的應用程序使用多個,這可能會變得難以維護。另一個重要的問題,您不希望每次更改其中一個屬性的值時需要重新部署,特別是在持續集成的時候。

爲解決這些問題,Netflix提出了一個開源的解決方案:Archaius。Archaius是Apache公共配置庫的擴展, 它允許您從多個動態源中檢索屬性,並且它解決了前面提到的所有問題(異構的屬性源,運行時更改等)。

我們先從用Archaius讀取屬性文件最簡單的示例開始。

public class ApplicationConfig {

  public String getStringProperty(String key, String defaultValue) {
    final DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty(key,
        defaultValue);
    return property.get();
  }
}

public class ApplicationConfigTest {
  private ApplicationConfig appConfig = new ApplicationConfig();

  @Test
  public void shouldRetrieveThePropertyByKey() {
    String property = appConfig.getStringProperty("hello.world.message", "default message");

    assertThat(property, is("Hello Archaius World!"));
  }

  @Test
  public void shouldRetrieveDefaultValueWhenKeyIsNotPresent() {
    String property = appConfig.getStringProperty("some.key", "default message");

    assertThat(property, is("default message"));
  }
}

該代碼是讀取類路徑中某處的“config.properties”文件。請注意,您不需要告訴Archaius在哪裏找到您的屬性文件,因爲他要查找的默認名稱是“config.properties”。

如果您不想或不能將屬性文件命名爲“config.property”,該怎麼辦?在這種情況下,您需要告訴Archaius在哪裏查找此文件。您可以輕鬆地更改系統屬性'archaius.configurationSource.defaultFileName',在啓動應用程序時將其作爲參數傳遞給vm:

java ... -Darchaius.configurationSource.defaultFileName=customName.properties

或者寫在代碼本身中:

public class ApplicationConfig {
  static {
    System.setProperty("archaius.configurationSource.defaultFileName", "customConfig.properties");
  }

  public String getStringProperty(String key, String defaultValue) {
    final DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty(key,
        defaultValue);
    return property.get();
  }
}

現在,如果你想讀幾個屬性文件怎麼辦?您可以從首先加載的默認文件開始,輕鬆定義屬性文件鏈及其加載順序。從那裏,您可以使用鍵“@ next = nextFile.properties”指定一個特殊屬性來告訴Archaius哪個是應該加載的下一個文件。

在我們的示例中,我們可以在“customConfig.properties”文件中添加以下行:

@next=secondConfig.properties

並將相應的“secondConfig.properties”添加到我們的resources文件夾中,其中包含以下內容:

cascade.property=cascade value

我們可以通過在ApplicationConfigTest類中添加以下測試來驗證:

@Test
public void shouldReadCascadeConfigurationFiles() {
    String property = appConfig.getStringProperty("cascade.property", "not found");

    assertThat(property, is("cascade value"));
}

請注意,我們從新文件中獲取屬性,而不對ApplicationConfig類進行任何其他更改。從客戶的角度來看,這是完全透明的。

到目前爲止,我們一直在從不同的屬性文件中讀取屬性,但如果您想從不同的源讀取它們會怎麼樣?在最常見的情況下,您可以通過實現“com.netflix.config.PolledConfigurationSource”來編寫自己的邏輯。如果新源是可以通過JDBC訪問的數據庫,那麼Archaius已經提供了可以使用的“JDBCConfigurationSource”。您只需要告訴他應該使用什麼查詢來獲取屬性以及哪些列表示屬性鍵和屬性值。

我們的例子如下:

@Component
public class ApplicationConfig {
  static {
    System.setProperty("archaius.configurationSource.defaultFileName", "customConfig.properties");
  }

  private final DataSource dataSource;

  @Autowired
    public ApplicationConfig(DataSource dataSource) {
      this.dataSource = dataSource;
      installJdbcSource();
    }

  public String getStringProperty(String key, String defaultValue) {
    final DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty(key,
        defaultValue);
    return property.get();
  }

  private DynamicConfiguration installJdbcSource() {
    if (!isConfigurationInstalled()) {
        PolledConfigurationSource source = new JDBCConfigurationSource(dataSource,
                "select distinct property_key, property_value from properties", "property_key", "property_value");
        DynamicConfiguration configuration = new DynamicConfiguration(source,
                new FixedDelayPollingScheduler(0, 10000, true));

        ConfigurationManager.install(configuration);
        return configuration;
    }
    return null;
  }
}

我們使用Spring來自動裝配數據源,該數據源將使用具有簡單鍵/值表的基於內存的H2數據庫。注意我們是如何創建一個新的 PolledConfigurationSource的,使用Archaius已經提供的JDBCConfigurationSource,然後我們註冊使用新的配置 ConfigurationManager。執行此操作後,我們可以從數據庫中獲取任何屬性,就像我們對屬性文件所做的那樣(即使用 DynamicPropertyFactory)。

我們現在可以添加幾個測試類來確保我們實際上從數據庫中讀取屬性,並且我們可以更新它們的值並查看動態配置中反映的更改。

@Test
public void shouldRetrievePropertyFromDB() {
    String property = appConfig.getStringProperty("db.property", "default message");

    assertThat(property, is("this is a db property"));
}

@Test
public void shouldReadTheNewValueAfterTheSpecifiedDelay() throws InterruptedException, SQLException {
    template.update("update properties set property_value = 'changed value' where property_key = 'db.property'");

    String propertyValue = (String) template.queryForObject(
            "select property_value from properties where property_key = 'db.property'", java.lang.String.class);
    System.out.println(propertyValue);

    String property = appConfig.getStringProperty("db.property", "default message");

    // We updated the value on the DB but Archaius polls for changes every 10000
    // milliseconds so it still sees the old value
    assertThat(property, is("this is a db property"));

    Thread.sleep(30000);

    property = appConfig.getStringProperty("db.property", "default message");
    assertThat(property, is("changed value"));
}

Archaius提供的另一個非常酷的功能是可以通過JMX 將我們的配置註冊爲 MBean。我們可以默認設置系統屬性 archaius.dynamicPropertyFactory.registerConfigWithJMX = true或使用ConfigJMXManager.registerConfigMbean(config)進行編程。

執行此操作後,我們可以通過JConsole連接,不僅可以獲取所有屬性的值,還可以更新它們並查看它們在Archaius中反映的新值。例如,這將允許我們在運行時更改屬性文件中靜態定義的屬性值,而無需服務器推送。我們可以稍微修改一下ApplicationConfig類來添加一個main方法,該方法將持續運行打印不同屬性的值,這樣將允許我們在JConsole中使用。

public class ApplicationConfig extends Thread {

  private final DataSource dataSource;

  @Autowired
    public ApplicationConfig(DataSource dataSource) {
      this.dataSource = dataSource;
      cascadeDefaultConfiguration();
      DynamicConfiguration jdbcSource = installJdbcSource();
      registerMBean(jdbcSource);
    }

  public String getStringProperty(String key, String defaultValue) {
    final DynamicStringProperty property = DynamicPropertyFactory.getInstance().getStringProperty(key,
        defaultValue);
    return property.get();
  }

  @Override
    public void run() {
      while (true) {
        try {
          sleep(100);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }

      }
    }

  private void registerMBean(DynamicConfiguration jdbcSource) {
    setDaemon(false);
    ConfigJMXManager.registerConfigMbean(jdbcSource);
  }

  public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("archaiusContext.xml");
    ApplicationConfig applicationConfig = (ApplicationConfig) applicationContext.getBean("applicationConfig");

    applicationConfig.start();

    while (true) {
      try {
        System.out.println(applicationConfig.getStringProperty("hello.world.message", "default message"));
        System.out.println(applicationConfig.getStringProperty("cascade.property", "default message"));
        System.out.println(applicationConfig.getStringProperty("db.property", "default message"));
        sleep(3000);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }

    }
  }
}

您還可以使用Archaius進行更多操作,例如每次屬性更改時的回調,與Zookeeper或其他服務的集成。您可以在GitHub上找到本文中顯示的所有代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章