title: Apollo接入使用方式
author: Mood
tags:
- SpringCloud
categories: - Apollo
- Config
date: 2018-09-24 22:07:00
一、 准备工作
- 1.1 环境要求
- Java: 1.7+
- Guava: 15.0+
- Apollo客户端默认会引用Guava 19
注:对于Apollo客户端,如果有需要的话,可以做少量代码修改来降级到Java 1.6,详细信息可以参考Issue 483
1.2 AppId Apollo Meta Server
1.2.1 AppId
AppId是应用的身份信息
有以下几种方式设置,按照优先级从高到低分别为:
- System Property
Apollo 0.7.0+支持通过System Property传入app.id信息,如
-Dapp.id=APP-ID
- 操作系统的System Environment
Apollo 1.4.0+支持通过操作系统的System Environment APP_ID来传入app.id信息,如
APP_ID=APP-ID
- Spring Boot application.properties
Apollo 1.0.0+支持通过Spring Boot的application.properties文件配置,如
app.id=APP-ID
该配置方式不适用于多个war包部署在同一个tomcat的使用场景
- app.properties
在classpath:/META-INF/创建app.properties文件,并且其中内容形如:
app.id=APP-ID
1.2.2 Apollo Meta Server
MetaServer这个角色,它其实是一个Eureka的Proxy,将Eureka的服务发现接口以更简单明确的HTTP接口的形式暴露出来,方便Client/Protal通过简单的HTTPClient就可以查询到Config/AdminService的地址列表。获取到服务实例地址列表之后,再以简单的客户端软负载(Client SLB)策略路由定位到目标实例,并发起调用。
Apollo支持应用在不同的环境有不同的配置,所以需要在运行提供给Apollo客户端当前环境的Apollo Meta Server信息。默认情况下,meta server和config service是部署在同一个JVM进程,所以meta server的地址就是config service的地址。
1.0.0版本开始支持以下方式配置apollo meta server信息,按照优先级从高到低分别为:
- 通过Java System Property
apollo.meta 可以通过Java的System Property apollo.meta来指定
在Java程序启动脚本中,可以指定-Dapollo.meta=http://config-service-url,$_meta
如果当前env是dev,那么用户可以配置-Ddev_meta=http://config-service-url
如果是运行jar文件,需要注意格式是java -Dapollo.meta=http://config-service-url -jar xxx.jar
也可以通过程序指定,如System.setProperty("apollo.meta", "http://config-service-url");
- 通过Spring Boot的配置文件
可以在Spring Boot的application.properties或bootstrap.properties中指定apollo.meta=http://config-service-url
该配置方式不适用于多个war包部署在同一个tomcat的使用场景
通过操作系统的System EnvironmentAPOLLO_META
可以通过操作系统的System Environment APOLLO_META来指定
注意key为全大写,且中间是_分隔
- 通过server.properties配置文件
可以在server.properties配置文件中指定apollo.meta=http://config-service-url
- 对于Mac/Linux,文件位置为/opt/settings/server.properties
- 对于Windows,文件位置为C:\opt\settings\server.properties
- 通过app.properties配置文件
可以在classpath:/META-INF/app.properties指定apollo.meta=http://config-service-url
如果通过以上各种手段都无法获取到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的应用零配置使用。比如自己写一个dingxing-company-apollo-client,该jar包依赖apollo-client,在该jar包中通过spi方式定义自定义的MetaServerProvider实现,然后应用直接依赖dingxing-company-apollo-client即可。
MetaServerProvider的实现可以参考LegacyMetaServerProvider和DefaultMetaServerProvider。
1.2.2.2 跳过Apollo Meta Server服务发现
适用于apollo-client 0.11.0及以上版本
一般情况下都建议使用Apollo的Meta Server机制来实现Config Service的服务发现,从而可以实现Config Service的高可用。不过apollo-client也支持跳过Meta Server服务发现,主要用于以下场景:
- Config Service部署在公有云上,注册到Meta Server的是内网地址,本地开发环境无法直接连接
- Config Service部署在docker环境中,注册到Meta Server的是docker内网地址,本地开发环境无法直接连接
- Config Service部署在kubernetes中,希望使用kubernetes自带的服务发现能力(Service)
针对以上场景,可以通过直接指定Config Service地址的方式来跳过Meta Server服务发现,按照优先级从高到低分别为:
- 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"); - 通过操作系统的System EnvironmentAPOLLO_CONFIGSERVICE
可以通过操作系统的System Environment APOLLO_CONFIGSERVICE来指定
注意key为全大写,且中间是_分隔 - 通过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/data或C:\opt\data\目录存在,且应用有读写权限。
- Mac/Linux: /opt/data//config-cache
- Windows: C:\opt\data{appId}\config-cache
本地配置文件会以下面的文件名格式放置于本地缓存路径下:
{appId}+{cluster}+{namespace}.properties
- appId 就是应用自己的appId,如100004458
- cluster 就是应用使用的集群,一般在本地模式下没有做过配置的话,就是default
- namespace 就是应用使用的配置namespace,一般是application
文件内容以properties格式存储,比如如果有两个key,一个是request.timeout,另一个是batch,那么文件内容就是如下格式:
request.timeout=2000
batch=2000
1.2.3.1 自定义缓存路径
1.0.0版本开始支持以下方式自定义缓存路径,按照优先级从高到低分别为:和上述的优先级一样,指定apollo.cacheDir=/opt/data/some-cache-dir
即可
1.2.4.2 Cluster(集群)
Apollo支持配置按照集群划分,也就是说对于一个appId和一个环境,对不同的集群可以有不同的配置。
1.0.0版本开始支持以下方式集群,按照优先级从高到低分别为:
- Java System Property
可以通过Java的System Property apollo.cluster来指定
在Java程序启动脚本中,可以指定-Dapollo.cluster=SomeCluster
- 如果是运行jar文件
需要注意格式是java -Dapollo.cluster=SomeCluster -jar xxx.jar
也可以通过程序指定,如System.setProperty("apollo.cluster", "SomeCluster");
- Spring Boot的配置文件
可以在Spring Boot的application.properties或bootstrap.properties中指定apollo.cluster=SomeCluster
- 通过Java System Property
可以通过Java的System Propertyidc
来指定环境- 在Java程序启动脚本中,可以指定-Didc=xxx
- 如果是运行jar文件,需要注意格式是java -Didc=xxx -jar xxx.jar
注意key为全小写
- 通过操作系统的System Environment
还可以通过操作系统的System Environment IDC来指定
注意key为全大写
- 通过server.properties配置文件
可以在server.properties配置文件中指定idc=xxx
对于Mac/Linux,文件位置为/opt/settings/server.properties
对于Windows,文件位置为C:\opt\settings\server.properties
Cluster Precedence(集群顺序)
- 如果apollo.cluster和idc同时指定:
- 我们会首先尝试从apollo.cluster指定的集群加载配置
- 如果没找到任何配置
会尝试从idc指定的集群加载配置 - 如果还是没找到,会从默认的集群(default)加载
- 如果只指定了apollo.cluster:
- 我们会首先尝试从apollo.cluster指定的集群加载配置
- 如果没找到,会从默认的集群(default)加载
- 如果只指定了idc:
- 我们会首先尝试从idc指定的集群加载配置
- 如果没找到,会从默认的集群(default)加载
- 如果apollo.cluster和idc都没有指定:
- 我们会从默认的集群(default)加载配置
二、Maven Dependency
Apollo的客户端jar包已经上传到中央仓库,应用在实际使用时只需要按照如下方式引入即可。
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.1.0</version>
</dependency>
三、Apollo Client接入
Apollo支持API方式和SpringBoot整合方式,该怎么选择用哪一种方式?
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 SpringBoot整合方式
3.2.1 基于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.2 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中按照如下样例配置即可。
注入默认application namespace的配置示例
# will inject 'application' namespace in bootstrap phase
apollo.bootstrap.enabled = true
注入非默认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
3.2.2 Spring Placeholder的使用
Spring应用通常会使用Placeholder来注入配置,使用的格式形如$,如$。冒号前面的是key,冒号后面的是默认值。
建议在实际使用时尽量给出默认值,以免由于key没有定义导致运行时错误。
从v0.10.0开始的版本支持placeholder在运行时自动更新.
如果需要关闭placeholder在运行时自动更新功能,可以通过以下两种方式关闭:
-
通过设置System Property apollo.autoUpdateInjectedSpringProperties,如启动时传入-Dapollo.autoUpdateInjectedSpringProperties=false
-
通过设置META-INF/app.properties中的apollo.autoUpdateInjectedSpringProperties属性,如
app.id=SampleApp
apollo.autoUpdateInjectedSpringProperties=false
3.2.2.1 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中有timeout和batch的配置项):
@Configuration
@EnableApolloConfig
public class AppConfig {
@Bean
public TestJavaConfigBean javaConfigBean() {
return new TestJavaConfigBean();
}
}
3.2.2.2 ConfigurationProperties使用方式
Spring Boot提供了@ConfigurationProperties把配置注入到bean对象中。
Apollo也支持这种方式,下面的例子会把redis.cache.expireSeconds和redis.cache.commandTimeout分别注入到SampleRedisConfig的expireSeconds和commandTimeout字段中。
@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.expireSeconds和redis.cache.commandTimeout的配置项):
@Configuration
@EnableApolloConfig
public class AppConfig {
@Bean
public SampleRedisConfig sampleRedisConfig() {
return new SampleRedisConfig();
}
}
3.2.3 Spring Annotation支持
Apollo同时还增加了几个新的Annotation来简化在Spring环境中的使用。
- @ApolloConfig
用来自动注入Config对象
- @ApolloConfigChangeListener
用来自动注册ConfigChangeListener
- @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的,具体步骤如下:
- 在Apollo为应用新建项目
在应用中配置好META-INF/app.properties
建议把原先配置先转为properties格式,然后通过Apollo提供的文本编辑模式全部粘帖到应用的application namespace,发布配置 - 如果原来格式是yml,可以使用YamlPropertiesFactoryBean.getObject转成properties格式
如果原来是yml,想继续使用yml来编辑配置,那么可以创建私有的application.yml namespace
,把原来的配置全部粘贴进去,发布配置需要apollo-client是1.3.0及以上版本
把原先的配置文件如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