Apollo(阿波羅)配置中心Java客戶端使用指南使用指南

      Apollo(阿波羅)是攜程框架部門研發的分佈式配置中心,可以集中化管理應用不一樣環境、不一樣集羣的配置,配置修改後可以實時推送到應用端,而且具有規範的權限、流程治理等特性, 適用於微服務配置管理場景。
 

1、準備工做html

1.1 環境要求

  • Java: 1.7+
  • Guava: 15.0+
    • Apollo客戶端默認會引用Guava 19,若是你的項目引用了其它版本,請確保版本號大於等於15.0

注:對於Apollo客戶端,若是有須要的話,能夠作少許代碼修改來降級到Java 1.6,詳細信息能夠參考Issue 483java

1.2 必選設置

Apollo客戶端依賴於AppIdApollo Meta Server等環境信息來工做,因此請確保閱讀下面的說明而且作正確的配置:mysql

1.2.1 AppId

AppId是應用的身份信息,是從服務端獲取配置的一個重要信息。git

有如下幾種方式設置,按照優先級從高到低分別爲:github

  1. System Property

Apollo 0.7.0+支持經過System Property傳入app.id信息,如redis

-Dapp.id=YOUR-APP-ID
  1. 操做系統的System Environment

Apollo 1.4.0+支持經過操做系統的System Environment APP_ID來傳入app.id信息,如spring

APP_ID=YOUR-APP-ID
  1. Spring Boot application.properties

Apollo 1.0.0+支持經過Spring Boot的application.properties文件配置,如sql

app.id=YOUR-APP-ID

該配置方式不適用於多個war包部署在同一個tomcat的使用場景docker

  1. app.properties

確保classpath:/META-INF/app.properties文件存在,而且其中內容形如:shell

app.id=YOUR-APP-ID

文件位置參考以下:

app-id-location

注:app.id是用來標識應用身份的惟一id,格式爲string。

1.2.2 Apollo Meta Server

Apollo支持應用在不一樣的環境有不一樣的配置,因此須要在運行提供給Apollo客戶端當前環境的Apollo Meta Server信息。默認狀況下,meta server和config service是部署在同一個JVM進程,因此meta server的地址就是config service的地址。

爲了實現meta server的高可用,推薦經過SLB(Software Load Balancer)作動態負載均衡。Meta server地址也能夠填入IP,如http://1.1.1.1:8080,http://2.2.2.2:8080,不過生產環境仍是建議使用域名(走slb),由於機器擴容、縮容等均可能致使IP列表的變化。

1.0.0版本開始支持如下方式配置apollo meta server信息,按照優先級從高到低分別爲:

  1. 經過Java System Property apollo.meta
    • 能夠經過Java的System Property apollo.meta來指定
    • 在Java程序啓動腳本中,能夠指定-Dapollo.meta=http://config-service-url
      • 若是是運行jar文件,須要注意格式是java -Dapollo.meta=http://config-service-url -jar xxx.jar
    • 也能夠經過程序指定,如System.setProperty("apollo.meta", "http://config-service-url");
  2. 經過Spring Boot的配置文件
    • 能夠在Spring Boot的application.propertiesbootstrap.properties中指定apollo.meta=http://config-service-url

該配置方式不適用於多個war包部署在同一個tomcat的使用場景

  1. 經過操做系統的System EnvironmentAPOLLO_META
    • 能夠經過操做系統的System Environment APOLLO_META來指定
    • 注意key爲全大寫,且中間是_分隔
  2. 經過server.properties配置文件
    • 能夠在server.properties配置文件中指定apollo.meta=http://config-service-url
    • 對於Mac/Linux,文件位置爲/opt/settings/server.properties
    • 對於Windows,文件位置爲C:\opt\settings\server.properties
  3. 經過app.properties配置文件
    • 能夠在classpath:/META-INF/app.properties指定apollo.meta=http://config-service-url
  4. 經過Java system property ${env}_meta
    • 若是當前envdev,那麼用戶能夠配置-Ddev_meta=http://config-service-url
    • 使用該配置方式,那麼就必需要正確配置Environment,詳見1.2.4.1 Environment
  5. 經過操做系統的System Environment ${ENV}_META (1.2.0版本開始支持)
    • 若是當前envdev,那麼用戶能夠配置操做系統的System Environment DEV_META=http://config-service-url
    • 注意key爲全大寫
    • 使用該配置方式,那麼就必需要正確配置Environment,詳見1.2.4.1 Environment
  6. 經過apollo-env.properties文件
    • 用戶也能夠建立一個apollo-env.properties,放在程序的classpath下,或者放在spring boot應用的config目錄下
    • 使用該配置方式,那麼就必需要正確配置Environment,詳見1.2.4.1 Environment
    • 文件內容形如:
dev.meta=http://1.1.1.1:8080 fat.meta=http://apollo.fat.xxx.com uat.meta=http://apollo.uat.xxx.com pro.meta=http://apollo.xxx.com

若是經過以上各類手段都沒法獲取到Meta Server地址,Apollo最終會fallback到http://apollo.meta做爲Meta Server地址

1.2.2.1 自定義Apollo Meta Server地址定位邏輯

在1.0.0版本中,Apollo提供了MetaServerProvider SPI,用戶能夠注入本身的MetaServerProvider來自定義Meta Server地址定位邏輯。

因爲咱們使用典型的Java Service Loader模式,因此實現起來仍是比較簡單的。

有一點須要注意的是,apollo會在運行時按照順序遍歷全部的MetaServerProvider,直到某一個MetaServerProvider提供了一個非空的Meta Server地址,所以用戶須要格外注意自定義MetaServerProvider的Order。規則是較小的Order具備較高的優先級,所以Order=0的MetaServerProvider會排在Order=1的MetaServerProvider的前面。

若是你的公司有不少應用須要接入Apollo,建議封裝一個jar包,而後提供自定義的Apollo Meta Server定位邏輯,從而可讓接入Apollo的應用零配置使用。好比本身寫一個xx-company-apollo-client,該jar包依賴apollo-client,在該jar包中經過spi方式定義自定義的MetaServerProvider實現,而後應用直接依賴xx-company-apollo-client便可。

MetaServerProvider的實現能夠參考LegacyMetaServerProviderDefaultMetaServerProvider

1.2.2.2 跳過Apollo Meta Server服務發現

適用於apollo-client 0.11.0及以上版本

通常狀況下都建議使用Apollo的Meta Server機制來實現Config Service的服務發現,從而能夠實現Config Service的高可用。不過apollo-client也支持跳過Meta Server服務發現,主要用於如下場景:

  1. Config Service部署在公有云上,註冊到Meta Server的是內網地址,本地開發環境沒法直接鏈接
  2. Config Service部署在docker環境中,註冊到Meta Server的是docker內網地址,本地開發環境沒法直接鏈接
  3. Config Service部署在kubernetes中,但願使用kubernetes自帶的服務發現能力(Service)

針對以上場景,能夠經過直接指定Config Service地址的方式來跳過Meta Server服務發現,按照優先級從高到低分別爲:

  1. 經過Java System Property apollo.configService
    • 能夠經過Java的System Property apollo.configService來指定
    • 在Java程序啓動腳本中,能夠指定-Dapollo.configService=http://config-service-url:port
      • 若是是運行jar文件,須要注意格式是java -Dapollo.configService=http://config-service-url:port -jar xxx.jar
    • 也能夠經過程序指定,如System.setProperty("apollo.configService", "http://config-service-url:port");
  2. 經過操做系統的System EnvironmentAPOLLO_CONFIGSERVICE
    • 能夠經過操做系統的System Environment APOLLO_CONFIGSERVICE來指定
    • 注意key爲全大寫,且中間是_分隔
  3. 經過server.properties配置文件
    • 能夠在server.properties配置文件中指定apollo.configService=http://config-service-url:port
    • 對於Mac/Linux,文件位置爲/opt/settings/server.properties
    • 對於Windows,文件位置爲C:\opt\settings\server.properties

1.2.3 本地緩存路徑

Apollo客戶端會把從服務端獲取到的配置在本地文件系統緩存一份,用於在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置,不影響應用正常運行。

本地緩存路徑默認位於如下路徑,因此請確保/opt/dataC:\opt\data\目錄存在,且應用有讀寫權限。

  • Mac/Linux: /opt/data/{appId}/config-cache
  • Windows: C:\opt\data\{appId}\config-cache

本地配置文件會如下面的文件名格式放置於本地緩存路徑下:

{appId}+{cluster}+{namespace}.properties

  • appId就是應用本身的appId,如100004458
  • cluster就是應用使用的集羣,通常在本地模式下沒有作過配置的話,就是default
  • namespace就是應用使用的配置namespace,通常是application client-local-cache

文件內容以properties格式存儲,好比若是有兩個key,一個是request.timeout,另外一個是batch,那麼文件內容就是以下格式:

request.timeout=2000 batch=2000

1.2.3.1 自定義緩存路徑

1.0.0版本開始支持如下方式自定義緩存路徑,按照優先級從高到低分別爲:

  1. 經過Java System Property apollo.cacheDir
    • 能夠經過Java的System Property apollo.cacheDir來指定
    • 在Java程序啓動腳本中,能夠指定-Dapollo.cacheDir=/opt/data/some-cache-dir
      • 若是是運行jar文件,須要注意格式是java -Dapollo.cacheDir=/opt/data/some-cache-dir -jar xxx.jar
    • 也能夠經過程序指定,如System.setProperty("apollo.cacheDir", "/opt/data/some-cache-dir");
  2. 經過Spring Boot的配置文件
    • 能夠在Spring Boot的application.propertiesbootstrap.properties中指定apollo.cacheDir=/opt/data/some-cache-dir
  3. 經過操做系統的System EnvironmentAPOLLO_CACHEDIR
    • 能夠經過操做系統的System Environment APOLLO_CACHEDIR來指定
    • 注意key爲全大寫,且中間是_分隔
  4. 經過server.properties配置文件
    • 能夠在server.properties配置文件中指定apollo.cacheDir=/opt/data/some-cache-dir
    • 對於Mac/Linux,文件位置爲/opt/settings/server.properties
    • 對於Windows,文件位置爲C:\opt\settings\server.properties

注:本地緩存路徑也可用於容災目錄,若是應用在全部config service都掛掉的狀況下須要擴容,那麼也能夠先把配置從已有機器上的緩存路徑複製到新機器上的相同緩存路徑

1.2.4 可選設置

1.2.4.1 Environment

Environment能夠經過如下3種方式的任意一個配置:

  1. 經過Java System Property

    • 能夠經過Java的System Property env來指定環境
    • 在Java程序啓動腳本中,能夠指定-Denv=YOUR-ENVIRONMENT
      • 若是是運行jar文件,須要注意格式是java -Denv=YOUR-ENVIRONMENT -jar xxx.jar
    • 注意key爲全小寫
  2. 經過操做系統的System Environment

    • 還能夠經過操做系統的System Environment ENV來指定
    • 注意key爲全大寫
  3. 經過配置文件

    • 最後一個推薦的方式是經過配置文件來指定env=YOUR-ENVIRONMENT
    • 對於Mac/Linux,文件位置爲/opt/settings/server.properties
    • 對於Windows,文件位置爲C:\opt\settings\server.properties

文件內容形如:

env=DEV

目前,env支持如下幾個值(大小寫不敏感):

  • DEV
    • Development environment
  • FAT
    • Feature Acceptance Test environment
  • UAT
    • User Acceptance Test environment
  • PRO
    • Production environment

更多環境定義,能夠參考Env.java

1.2.4.2 Cluster(集羣)

Apollo支持配置按照集羣劃分,也就是說對於一個appId和一個環境,對不一樣的集羣能夠有不一樣的配置。

1.0.0版本開始支持如下方式集羣,按照優先級從高到低分別爲:

  1. 經過Java System Property apollo.cluster
    • 能夠經過Java的System Property apollo.cluster來指定
    • 在Java程序啓動腳本中,能夠指定-Dapollo.cluster=SomeCluster
      • 若是是運行jar文件,須要注意格式是java -Dapollo.cluster=SomeCluster -jar xxx.jar
    • 也能夠經過程序指定,如System.setProperty("apollo.cluster", "SomeCluster");
  2. 經過Spring Boot的配置文件
    • 能夠在Spring Boot的application.propertiesbootstrap.properties中指定apollo.cluster=SomeCluster
  3. 經過Java System Property
    • 能夠經過Java的System Property idc來指定環境
    • 在Java程序啓動腳本中,能夠指定-Didc=xxx
      • 若是是運行jar文件,須要注意格式是java -Didc=xxx -jar xxx.jar
    • 注意key爲全小寫
  4. 經過操做系統的System Environment
    • 還能夠經過操做系統的System Environment IDC來指定
    • 注意key爲全大寫
  5. 經過server.properties配置文件
    • 能夠在server.properties配置文件中指定idc=xxx
    • 對於Mac/Linux,文件位置爲/opt/settings/server.properties
    • 對於Windows,文件位置爲C:\opt\settings\server.properties

Cluster Precedence(集羣順序)

  1. 若是apollo.clusteridc同時指定:

    • 咱們會首先嚐試從apollo.cluster指定的集羣加載配置
    • 若是沒找到任何配置,會嘗試從idc指定的集羣加載配置
    • 若是仍是沒找到,會從默認的集羣(default)加載
  2. 若是隻指定了apollo.cluster

    • 咱們會首先嚐試從apollo.cluster指定的集羣加載配置
    • 若是沒找到,會從默認的集羣(default)加載
  3. 若是隻指定了idc

    • 咱們會首先嚐試從idc指定的集羣加載配置
    • 若是沒找到,會從默認的集羣(default)加載
  4. 若是apollo.clusteridc都沒有指定:

    • 咱們會從默認的集羣(default)加載配置

1.2.4.3 設置內存中的配置項是否保持和頁面上的順序一致

適用於1.6.0及以上版本

默認狀況下,apollo client內存中的配置存放在Properties中(底下是Hashtable),不會刻意保持和頁面上看到的順序一致,對絕大部分的場景是沒有影響的。不過有些場景會強依賴配置項的順序(如spring cloud zuul的路由規則),針對這種狀況,能夠開啓OrderedProperties特性來使得內存中的配置順序和頁面上看到的一致。

配置方式按照優先級從高到低分別爲:

  1. 經過Java System Property apollo.property.order.enable
    • 能夠經過Java的System Property apollo.property.order.enable來指定
    • 在Java程序啓動腳本中,能夠指定-Dapollo.property.order.enable=true
      • 若是是運行jar文件,須要注意格式是java -Dapollo.property.order.enable=true -jar xxx.jar
    • 也能夠經過程序指定,如System.setProperty("apollo.property.order.enable", "true");
  2. 經過Spring Boot的配置文件
    • 能夠在Spring Boot的application.propertiesbootstrap.properties中指定apollo.property.order.enable=true
  3. 經過app.properties配置文件
    • 能夠在classpath:/META-INF/app.properties指定apollo.property.order.enable=true

2、Maven Dependency

Apollo的客戶端jar包已經上傳到中央倉庫,應用在實際使用時只須要按照以下方式引入便可。

<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.1.0</version> </dependency>

3、客戶端用法

Apollo支持API方式和Spring整合方式,該怎麼選擇用哪種方式?

  • API方式靈活,功能完備,配置值實時更新(熱發佈),支持全部Java環境。
  • Spring方式接入簡單,結合Spring有N種酷炫的玩法,如
    • Placeholder方式:
      • 代碼中直接使用,如:@Value("${someKeyFromApollo:someDefaultValue}")
      • 配置文件中使用替換placeholder,如:spring.datasource.url: ${someKeyFromApollo:someDefaultValue}
      • 直接託管spring的配置,如在apollo中直接配置spring.datasource.url=jdbc:mysql://localhost:3306/somedb?characterEncoding=utf8
    • Spring boot的@ConfigurationProperties方式
    • 從v0.10.0開始的版本支持placeholder在運行時自動更新,具體參見PR #972。(v0.10.0以前的版本在配置變化後不會從新注入,須要重啓纔會更新,若是須要配置值實時更新,能夠參考後續3.2.2 Spring Placeholder的使用的說明)
  • Spring方式也能夠結合API方式使用,如注入Apollo的Config對象,就能夠照常經過API方式獲取配置了:
    @ApolloConfig private Config config; //inject config for namespace application
  • 更多有意思的實際使用場景和示例代碼,請參考apollo-use-cases

3.1 API使用方式

API方式是最簡單、高效使用Apollo配置的方式,不依賴Spring框架便可使用。

3.1.1 獲取默認namespace的配置(application)

Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never null String someKey = "someKeyFromDefaultNamespace"; String someDefaultValue = "someDefaultValueForTheKey"; String value = config.getProperty(someKey, someDefaultValue);

經過上述的config.getProperty能夠獲取到someKey對應的實時最新的配置值。

另外,配置值從內存中獲取,因此不須要應用本身作緩存。

3.1.2 監聽配置變化事件

監聽配置變化事件只在應用真的關心配置變化,須要在配置變化時獲得通知時使用,好比:數據庫鏈接串變化後須要重建鏈接等。

若是隻是但願每次都取到最新的配置的話,只須要按照上面的例子,調用config.getProperty便可。

Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never null config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { System.out.println("Changes for namespace " + changeEvent.getNamespace()); for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); System.out.println(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType())); } } });

3.1.3 獲取公共Namespace的配置

String somePublicNamespace = "CAT"; Config config = ConfigService.getConfig(somePublicNamespace); //config instance is singleton for each namespace and is never null String someKey = "someKeyFromPublicNamespace"; String someDefaultValue = "someDefaultValueForTheKey"; String value = config.getProperty(someKey, someDefaultValue);

3.1.4 獲取非properties格式namespace的配置

3.1.4.1 yaml/yml格式的namespace

apollo-client 1.3.0版本開始對yaml/yml作了更好的支持,使用起來和properties格式一致。

Config config = ConfigService.getConfig("application.yml"); String someKey = "someKeyFromYmlNamespace"; String someDefaultValue = "someDefaultValueForTheKey"; String value = config.getProperty(someKey, someDefaultValue);

3.1.4.2 非yaml/yml格式的namespace

獲取時須要使用ConfigService.getConfigFile接口並指定Format,如ConfigFileFormat.XML

String someNamespace = "test"; ConfigFile configFile = ConfigService.getConfigFile("test", ConfigFileFormat.XML); String content = configFile.getContent();

3.2 Spring整合方式

3.2.1 配置

Apollo也支持和Spring整合(Spring 3.1.1+),只須要作一些簡單的配置就能夠了。

Apollo目前既支持比較傳統的基於XML的配置,也支持目前比較流行的基於Java(推薦)的配置。

若是是Spring Boot環境,建議參照3.2.1.3 Spring Boot集成方式(推薦)配置。

須要注意的是,若是以前有使用org.springframework.beans.factory.config.PropertyPlaceholderConfigurer的,請替換成org.springframework.context.support.PropertySourcesPlaceholderConfigurer。Spring 3.1之後就不建議使用PropertyPlaceholderConfigurer了,要改用PropertySourcesPlaceholderConfigurer。

若是以前有使用<context:property-placeholder>,請注意xml中引入的spring-context.xsd版本須要是3.1以上(通常只要沒有指定版本會自動升級的),建議使用不帶版本號的形式引入,如:http://www.springframework.org/schema/context/spring-context.xsd

注1:yaml/yml格式的namespace從1.3.0版本開始支持和Spring整合,注入時須要填寫帶後綴的完整名字,好比application.yml

注2:非properties、非yaml/yml格式(如xml,json等)的namespace暫不支持和Spring整合。

3.2.1.1 基於XML的配置

注:須要把apollo相關的xml namespace加到配置文件頭上,否則會報xml語法錯誤。

1.注入默認namespace的配置到Spring中

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> <!-- 這個是最簡單的配置形式,通常應用用這種形式就能夠了,用來指示Apollo注入application namespace的配置到Spring環境中 --> <apollo:config/> <bean class="com.ctrip.framework.apollo.spring.TestXmlBean"> <property name="timeout" value="${timeout:100}"/> <property name="batch" value="${batch:200}"/> </bean> </beans>

2.注入多個namespace的配置到Spring中

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> <!-- 這個是最簡單的配置形式,通常應用用這種形式就能夠了,用來指示Apollo注入application namespace的配置到Spring環境中 --> <apollo:config/> <!-- 這個是稍微複雜一些的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring環境中 --> <apollo:config namespaces="FX.apollo,application.yml"/> <bean class="com.ctrip.framework.apollo.spring.TestXmlBean"> <property name="timeout" value="${timeout:100}"/> <property name="batch" value="${batch:200}"/> </bean> </beans>

3.注入多個namespace,而且指定順序

Spring的配置是有順序的,若是多個property source都有同一個key,那麼最終是順序在前的配置生效。

apollo:config若是不指定order,那麼默認是最低優先級。

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> <apollo:config order="2"/> <!-- 這個是最複雜的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring環境中,而且順序在application前面 --> <apollo:config namespaces="FX.apollo,application.yml" order="1"/> <bean class="com.ctrip.framework.apollo.spring.TestXmlBean"> <property name="timeout" value="${timeout:100}"/> <property name="batch" value="${batch:200}"/> </bean> </beans>

3.2.1.2 基於Java的配置(推薦)

相對於基於XML的配置,基於Java的配置是目前比較流行的方式。

注意@EnableApolloConfig要和@Configuration一塊兒使用,否則不會生效。

1.注入默認namespace的配置到Spring中

//這個是最簡單的配置形式,通常應用用這種形式就能夠了,用來指示Apollo注入application namespace的配置到Spring環境中 @Configuration @EnableApolloConfig public class AppConfig { @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } }

2.注入多個namespace的配置到Spring中

@Configuration @EnableApolloConfig public class SomeAppConfig { @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } } //這個是稍微複雜一些的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring環境中 @Configuration @EnableApolloConfig({"FX.apollo", "application.yml"}) public class AnotherAppConfig {}

3.注入多個namespace,而且指定順序

//這個是最複雜的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring環境中,而且順序在application前面 @Configuration @EnableApolloConfig(order = 2) public class SomeAppConfig { @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } } @Configuration @EnableApolloConfig(value = {"FX.apollo", "application.yml"}, order = 1) public class AnotherAppConfig {}

3.2.1.3 Spring Boot集成方式(推薦)

Spring Boot除了支持上述兩種集成方式之外,還支持經過application.properties/bootstrap.properties來配置,該方式能使配置在更早的階段注入,好比使用@ConditionalOnProperty的場景或者是有一些spring-boot-starter在啓動階段就須要讀取配置作一些事情(如dubbo-spring-boot-project),因此對於Spring Boot環境建議經過如下方式來接入Apollo(須要0.10.0及以上版本)。

使用方式很簡單,只須要在application.properties/bootstrap.properties中按照以下樣例配置便可。

  1. 注入默認application namespace的配置示例
# will inject 'application' namespace in bootstrap phase apollo.bootstrap.enabled = true
  1. 注入非默認application namespace或多個namespace的配置示例
apollo.bootstrap.enabled = true # will inject 'application', 'FX.apollo' and 'application.yml' namespaces in bootstrap phase apollo.bootstrap.namespaces = application,FX.apollo,application.yml
  1. 將Apollo配置加載提到初始化日誌系統以前(1.2.0+)

從1.2.0版本開始,若是但願把日誌相關的配置(如logging.level.root=infologback-spring.xml中的參數)也放在Apollo管理,那麼能夠額外配置apollo.bootstrap.eagerLoad.enabled=true來使Apollo的加載順序放到日誌系統加載以前,不過這會致使Apollo的啓動過程沒法經過日誌的方式輸出(由於執行Apollo加載的時候,日誌系統壓根沒有準備好呢!因此在Apollo代碼中使用Slf4j的日誌輸出便沒有任何內容),更多信息能夠參考PR 1614。參考配置示例以下:

# will inject 'application' namespace in bootstrap phase apollo.bootstrap.enabled = true # put apollo initialization before logging system initialization apollo.bootstrap.eagerLoad.enabled=true

3.2.2 Spring Placeholder的使用

Spring應用一般會使用Placeholder來注入配置,使用的格式形如${someKey:someDefaultValue},如${timeout:100}。冒號前面的是key,冒號後面的是默認值。

建議在實際使用時儘可能給出默認值,以避免因爲key沒有定義致使運行時錯誤。

從v0.10.0開始的版本支持placeholder在運行時自動更新,具體參見PR #972

若是須要關閉placeholder在運行時自動更新功能,能夠經過如下兩種方式關閉:

  1. 經過設置System Property apollo.autoUpdateInjectedSpringProperties,如啓動時傳入-Dapollo.autoUpdateInjectedSpringProperties=false

  2. 經過設置META-INF/app.properties中的apollo.autoUpdateInjectedSpringProperties屬性,如

app.id=SampleApp apollo.autoUpdateInjectedSpringProperties=false

3.2.2.1 XML使用方式

假設我有一個TestXmlBean,它有兩個配置項須要注入:

public class TestXmlBean { private int timeout; private int batch; public void setTimeout(int timeout) { this.timeout = timeout; } public void setBatch(int batch) { this.batch = batch; } public int getTimeout() { return timeout; } public int getBatch() { return batch; } }

那麼,我在XML中會使用以下方式來定義(假設應用默認的application namespace中有timeout和batch的配置項):

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> <apollo:config/> <bean class="com.ctrip.framework.apollo.spring.TestXmlBean"> <property name="timeout" value="${timeout:100}"/> <property name="batch" value="${batch:200}"/> </bean> </beans>

3.2.2.2 Java Config使用方式

假設我有一個TestJavaConfigBean,經過Java Config的方式還可使用@Value的方式注入:

public class TestJavaConfigBean { @Value("${timeout:100}") private int timeout; private int batch; @Value("${batch:200}") public void setBatch(int batch) { this.batch = batch; } public int getTimeout() { return timeout; } public int getBatch() { return batch; } }

在Configuration類中按照下面的方式使用(假設應用默認的application namespace中有timeoutbatch的配置項):

@Configuration @EnableApolloConfig public class AppConfig { @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } }

3.2.2.3 ConfigurationProperties使用方式

Spring Boot提供了@ConfigurationProperties把配置注入到bean對象中。

Apollo也支持這種方式,下面的例子會把redis.cache.expireSecondsredis.cache.commandTimeout分別注入到SampleRedisConfig的expireSecondscommandTimeout字段中。

@ConfigurationProperties(prefix = "redis.cache") public class SampleRedisConfig { private int expireSeconds; private int commandTimeout; public void setExpireSeconds(int expireSeconds) { this.expireSeconds = expireSeconds; } public void setCommandTimeout(int commandTimeout) { this.commandTimeout = commandTimeout; } }

在Configuration類中按照下面的方式使用(假設應用默認的application namespace中有redis.cache.expireSecondsredis.cache.commandTimeout的配置項):

@Configuration @EnableApolloConfig public class AppConfig { @Bean public SampleRedisConfig sampleRedisConfig() { return new SampleRedisConfig(); } }

須要注意的是,@ConfigurationProperties若是須要在Apollo配置變化時自動更新注入的值,須要配合使用EnvironmentChangeEventRefreshScope。相關代碼實現,能夠參考apollo-use-cases項目中的ZuulPropertiesRefresher.java和apollo-demo項目中的SampleRedisConfig.java以及SpringBootApolloRefreshConfig.java

3.2.3 Spring Annotation支持

Apollo同時還增長了幾個新的Annotation來簡化在Spring環境中的使用。

  1. @ApolloConfig
    • 用來自動注入Config對象
  2. @ApolloConfigChangeListener
    • 用來自動註冊ConfigChangeListener
  3. @ApolloJsonValue
    • 用來把配置的json字符串自動注入爲對象

使用樣例以下:

public class TestApolloAnnotationBean { @ApolloConfig private Config config; //inject config for namespace application @ApolloConfig("application") private Config anotherConfig; //inject config for namespace application @ApolloConfig("FX.apollo") private Config yetAnotherConfig; //inject config for namespace FX.apollo @ApolloConfig("application.yml") private Config ymlConfig; //inject config for namespace application.yml /**  * ApolloJsonValue annotated on fields example, the default value is specified as empty list - []  * <br />  * jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}]  */ @ApolloJsonValue("${jsonBeanProperty:[]}") private List<JsonBean> anotherJsonBeans; @Value("${batch:100}") private int batch; //config change listener for namespace application @ApolloConfigChangeListener private void someOnChange(ConfigChangeEvent changeEvent) { //update injected value of batch if it is changed in Apollo if (changeEvent.isChanged("batch")) { batch = config.getIntProperty("batch", 100); } } //config change listener for namespace application @ApolloConfigChangeListener("application") private void anotherOnChange(ConfigChangeEvent changeEvent) { //do something } //config change listener for namespaces application, FX.apollo and application.yml @ApolloConfigChangeListener({"application", "FX.apollo", "application.yml"}) private void yetAnotherOnChange(ConfigChangeEvent changeEvent) { //do something } //example of getting config from Apollo directly //this will always return the latest value of timeout public int getTimeout() { return config.getIntProperty("timeout", 200); } //example of getting config from injected value //the program needs to update the injected value when batch is changed in Apollo using @ApolloConfigChangeListener shown above public int getBatch() { return this.batch; } private static class JsonBean{ private String someString; private int someInt; } }

在Configuration類中按照下面的方式使用:

@Configuration @EnableApolloConfig public class AppConfig { @Bean public TestApolloAnnotationBean testApolloAnnotationBean() { return new TestApolloAnnotationBean(); } }

3.2.4 已有配置遷移

不少狀況下,應用可能已經有很多配置了,好比Spring Boot的應用,就會有bootstrap.properties/yml, application.properties/yml等配置。

在應用接入Apollo以後,這些配置是能夠很是方便的遷移到Apollo的,具體步驟以下:

  1. 在Apollo爲應用新建項目
  2. 在應用中配置好META-INF/app.properties
  3. 建議把原先配置先轉爲properties格式,而後經過Apollo提供的文本編輯模式所有粘帖到應用的application namespace,發佈配置
  4. 若是原來是yml,想繼續使用yml來編輯配置,那麼能夠建立私有的application.yml namespace,把原來的配置所有粘貼進去,發佈配置
    • 須要apollo-client是1.3.0及以上版本
  5. 把原先的配置文件如bootstrap.properties/yml, application.properties/yml從項目中刪除
    • 若是須要保留本地配置文件,須要注意部分配置如server.port必須確保本地文件已經刪除該配置項

如:

spring.application.name = reservation-service server.port = 8080 logging.level = ERROR eureka.client.serviceUrl.defaultZone = http://127.0.0.1:8761/eureka/ eureka.client.healthcheck.enabled = true eureka.client.registerWithEureka = true eureka.client.fetchRegistry = true eureka.client.eurekaServiceUrlPollIntervalSeconds = 60 eureka.instance.preferIpAddress = true

text-mode-spring-boot-config-sample

3.3 Demo

項目中有一個樣例客戶端的項目:apollo-demo,具體信息能夠參考Apollo開發指南中的2.3 Java樣例客戶端啓動部分。

更多使用案例Demo能夠參考Apollo使用場景和示例代碼

4、客戶端設計

client-architecture

上圖簡要描述了Apollo客戶端的實現原理:

  1. 客戶端和服務端保持了一個長鏈接,從而能第一時間得到配置更新的推送。(經過Http Long Polling實現)
  2. 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
    • 這是一個fallback機制,爲了防止推送機制失效致使配置不更新
    • 客戶端定時拉取會上報本地版本,因此通常狀況下,對於定時拉取的操做,服務端都會返回304 - Not Modified
    • 定時頻率默認爲每5分鐘拉取一次,客戶端也能夠經過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位爲分鐘。
  3. 客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中
  4. 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
    • 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
  5. 應用程序能夠從Apollo客戶端獲取最新的配置、訂閱配置更新通知

5、本地開發模式

Apollo客戶端還支持本地開發模式,這個主要用於當開發環境沒法鏈接Apollo服務器的時候,好比在郵輪、飛機上作相關功能開發。

在本地開發模式下,Apollo只會從本地文件讀取配置信息,不會從Apollo服務器讀取配置。

能夠經過下面的步驟開啓Apollo本地開發模式。

5.1 修改環境

修改/opt/settings/server.properties(Mac/Linux)或C:\opt\settings\server.properties(Windows)文件,設置env爲Local:

env=Local

更多配置環境的方式請參考1.2.2 Environment

5.2 準備本地配置文件

在本地開發模式下,Apollo客戶端會從本地讀取文件,因此咱們須要事先準備好配置文件。

5.2.1 本地配置目錄

本地配置目錄位於:

  • Mac/Linux: /opt/data/{appId}/config-cache
  • Windows: C:\opt\data\{appId}\config-cache

appId就是應用的appId,如100004458。

請確保該目錄存在,且應用程序對該目錄有讀權限。

【小技巧】 推薦的方式是先在普通模式下使用Apollo,這樣Apollo會自動建立該目錄並在目錄下生成配置文件。

5.2.2 本地配置文件

本地配置文件須要按照必定的文件名格式放置於本地配置目錄下,文件名格式以下:

{appId}+{cluster}+{namespace}.properties

  • appId就是應用本身的appId,如100004458
  • cluster就是應用使用的集羣,通常在本地模式下沒有作過配置的話,就是default
  • namespace就是應用使用的配置namespace,通常是application client-local-cache

文件內容以properties格式存儲,好比若是有兩個key,一個是request.timeout,另外一個是batch,那麼文件內容就是以下格式:

request.timeout=2000 batch=2000

5.3 修改配置

在本地開發模式下,Apollo不會實時監測文件內容是否有變化,因此若是修改了配置,須要重啓應用生效。

6、測試模式

1.1.0版本開始增長了apollo-mockserver,從而能夠很好地支持單元測試時須要mock配置的場景,使用方法以下:

6.1 引入pom依賴

<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-mockserver</artifactId> <version>1.1.0</version> </dependency> 

6.2 在test的resources下放置mock的數據

文件名格式約定爲mockdata-{namespace}.properties

image

6.3 寫測試類

更多使用demo能夠參考ApolloMockServerApiTest.javaApolloMockServerSpringIntegrationTest.java

@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = TestConfiguration.class) public class SpringIntegrationTest { // 啓動apollo的mockserver @ClassRule public static EmbeddedApollo embeddedApollo = new EmbeddedApollo(); @Test @DirtiesContext // 這個註解頗有必要,由於配置注入會弄髒應用上下文 public void testPropertyInject(){ assertEquals("value1", testBean.key1); assertEquals("value2", testBean.key2); } @Test @DirtiesContext public void testListenerTriggeredByAdd() throws InterruptedException, ExecutionException, TimeoutException { String otherNamespace = "othernamespace"; embeddedApollo.addOrModifyPropery(otherNamespace,"someKey","someValue"); ConfigChangeEvent changeEvent = testBean.futureData.get(5000, TimeUnit.MILLISECONDS); assertEquals(otherNamespace, changeEvent.getNamespace()); assertEquals("someValue", changeEvent.getChange("someKey").getNewValue()); } @EnableApolloConfig("application") @Configuration static class TestConfiguration{ @Bean public TestBean testBean(){ return new TestBean(); } } static class TestBean{ @Value("${key1:default}") String key1; @Value("${key2:default}") String key2; SettableFuture<ConfigChangeEvent> futureData = SettableFuture.create(); @ApolloConfigChangeListener("othernamespace") private void onChange(ConfigChangeEvent changeEvent) { futureData.set(changeEvent); } } }

附:wiki:https://github.com/ctripcorp/apollo/wiki/Java%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97
相關文章
相關標籤/搜索