apollo学习
之前有看apollo的一些文档,但是过一段时间就忘记了,这里整理一下,方便以后查看吧。文章的资料来自于
https://github.com/ctripcorp/apollo。
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。
Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。
一、apollo配置中心设计
1.1 基础模型
用户在配置中心对配置进行修改并发布
配置中心通知apollo客户端有配置更新
apollo客户端从配置中心拉取最新的配置,更新本地配置并通知到应用
1.2 架构设计
1.2.1 架构设计图
1.2.2 四个核心模块及其主要功能
configService
提供配置获取接口
提供配置推送接口
服务于apollo客户端
adminService
提供配置管理接口
提供配置修改发布接口
服务于管理界面portal
client
为应用获取配置,支持实时更新
通过MetaServer获取ConfigService的服务列表
使用客户关软负载均衡SLB方式调用ConfigService
portal
配置管理页面
通过MetaServer获取AdminService的服务列表
使用客户端软负载均衡SLB方式调用AdminService
1.2.3 三个辅助发现模块
Eureka
用户服务发现和注册
config/adminService注册实例并定期报心跳
和ConfigService住在一起部署
MetaServer
portal通过域名访问MetaServer获取AdminService的地址列表
Client通过域名访问MetaServer获取ConfigService的地址列表
相当于一个eureka
NginxLB
和域名系统配合,协助portal访问MetaServer获取AdminService地址列表
和域名系统配合,协助client访问MetaServer获取ConfigService地址列表
和域名系统配合,协助用户访问Portal进行配置管理
1.3 E-R Diagram
1.3.1 主体E-R
app
App信息
appnamespace
App下Namespace的元信息
cluster
集群信息
namespace
集群下的namespace
item
Namespace的配置,每个Item是一个key, value组合
release
Namespace发布的配置,每个发布包含发布时该Namespace的所有配置
commit
Namespace下的配置更改记录
audit
审计信息,记录用户在何时使用何种方式操作了哪个实体。
1.3.2 权限相关E-R Diagram
User
Apollo portal用户
UserRole
用户和角色的关系
Role
角色
RolePermission
角色和权限的关系
Permission
权限
对应到具体的实体资源和操作,如修改NamespaceA的配置,发布NamespaceB的配置等。
Consumer
第三方应用
ConsumerToken
发给第三方应用的token
ConsumerRole
第三方应用和角色的关系
ConsumerAudit
第三方应用访问审计
二、服务端设计
在配置中心中,一个重要的功能就是配置发布过后实时推动到客户端。大致过程如下:
用户在Portal操作配置发布
Portal调用Adminservice的接口操作发布
Adminservice发布配置后,发送releaseMessage给各个ConfigService
ConfigService收到releaseMessage后,通知对应的客户端
2.1.1 发送ReleaseMessage的实现方式
Admin Service在配置发布后,需要通知所有的Config Service有配置发布,从而Config Service可以通知对应的客户端来拉取最新的配置。
从概念上来看,这是一个典型的消息使用场景,Admin Service作为producer发出消息,各个Config Service作为consumer消费消息。通过一个消息组件(Message Queue)就能很好的实现Admin Service和Config Service的解耦。
在实现上,考虑到Apollo的实际使用场景,以及为了尽可能减少外部依赖,我们没有采用外部的消息中间件,而是通过数据库实现了一个简单的消息队列。
实现方式如下:
Admin Service在配置发布后会往ReleaseMessage表插入一条消息记录,消息内容就是配置发布的AppId+Cluster+Namespace,参见DatabaseMessageSender
Config Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录,参见ReleaseMessageScanner
Config Service如果发现有新的消息记录,那么就会通知到所有的消息监听器(ReleaseMessageListener),如NotificationControllerV2,消息监听器的注册过程参见ConfigServiceAutoConfiguration
NotificationControllerV2得到配置发布的AppId+Cluster+Namespace后,会通知对应的客户端
2.1.2 Config Service通知客户端的实现方式
上一节中简要描述了NotificationControllerV2是如何得知有配置发布的,那NotificationControllerV2在得知有配置发布后是如何通知到客户端的呢?
实现方式如下:
客户端会发起一个Http请求到Config Service的
notifications/v2
接口,也就是NotificationControllerV2,参见RemoteConfigLongPollServiceNotificationControllerV2不会立即返回结果,而是通过Spring DeferredResult把请求挂起
如果在60秒内没有该客户端关心的配置发布,那么会返回Http状态码304给客户端
如果有该客户端关心的配置发布,NotificationControllerV2会调用DeferredResult的setResult方法,传入有配置变化的namespace信息,同时该请求会立即返回。客户端从返回的结果中获取到配置变化的namespace后,会立即请求Config Service获取该namespace的最新配置。
三、客户端设计
上图简要描述了Apollo客户端的实现原理:
客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)
客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。
这是一个fallback机制,为了防止推送机制失效导致配置不更新
客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property:
apollo.refreshInterval
来覆盖,单位为分钟。
客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
客户端会把从服务端获取到的配置在本地文件系统缓存一份
在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知
3.1 和Spring集成的原理
Apollo除了支持API方式获取配置,也支持和Spring/Spring Boot集成,集成原理简述如下。
Spring从3.1版本开始增加了ConfigurableEnvironment
和PropertySource
:
ConfigurableEnvironment
Spring的ApplicationContext会包含一个Environment(实现ConfigurableEnvironment接口)
ConfigurableEnvironment自身包含了很多个PropertySource
PropertySource
属性源
可以理解为很多个Key - Value的属性配置
需要注意的是,PropertySource之间是有优先级顺序的,如果有一个Key在多个property source中都存在,那么在前面的property source优先。
所以对上图的例子:
env.getProperty(“key1”) -> value1
env.getProperty(“key2”) -> value2
env.getProperty(“key3”) -> value4
在理解了上述原理后,Apollo和Spring/Spring Boot集成的手段就呼之欲出了:在应用启动阶段,Apollo从远端获取配置,然后组装成PropertySource并插入到第一个即可
相关代码可以参考PropertySourcesProcessor
四、apollo分布式部署指南
4.1 数据库表创建
新搭建时:
执行sql文件:apolloportaldb.sql和apolloconfigdb.sql创建表
从别的环境移植时
主要讲将ApolloConfigDB
.
App。ApolloConfigDB.
AppNamespace。ApolloConfigDB.
Cluster。ApolloConfigDB.
Namespace。这几个表中的数据加入到新表中。同时也别忘了通知用户在新的环境给自己的项目设置正确的配置信息,尤其是一些影响面比较大的公共namespace配置。
如果是为正在运行的环境迁移数据,建议迁移完重启一下config service,因为config service中有appnamespace的缓存数据。
需要注意的是ApolloPortalDB只需要在生产环境部署一个即可,而ApolloConfigDB需要在每个环境部署一套,如fat、uat和pro分别部署3套ApolloConfigDB。
4.2 调整服务端配置
Apollo自身的一些配置是放在数据库里面的,所以需要针对实际情况做一些调整。
以下配置除了支持在数据库中配置以外,也支持通过-D参数、application.properties等配置,且-D参数、application.properties等优先级高于数据库中的配置
4.2.1 调整ApolloPortalDB配置
配置项统一存储在ApolloPortalDB.ServerConfig表中,也可以通过管理员工具 - 系统参数
页面进行配置,无特殊说明则修改完一分钟实时生效。
apollo.portal.envs - 可支持的环境列表
DEV,FAT,UAT,PRO
apollo.portal.meta.servers - 各环境Meta Service列表
{
"DEV":"http://1.1.1.1:8080",
"FAT":"http://apollo.fat.xxx.com",
"UAT":"http://apollo.uat.xxx.com",
"PRO":"http://apollo.xxx.com"
}superAdmin - Portal超级管理员
organizations-部门列表
[{"orgId":"TEST1","orgName":"样例部门1"},{"orgId":"TEST2","orgName":"样例部门2"}]
configView.memberOnly.envs
只对项目成员显示配置信息的环境列表,多个env以英文逗号分隔.
对设定了只对项目成员显示配置信息的环境,只有该项目的管理员或拥有该namespace的编辑或发布权限的用户才能看到该私有namespace的配置信息和发布历史。公共namespace始终对所有用户可见。
role.manage-app-master.enabled - 是否开启项目管理员分配权限控制
如果设置为true,那么只有超级管理员和拥有项目管理员分配权限的帐号可以为特定项目添加/删除管理员,超级管理员可以通过
管理员工具 - 系统权限管理
给用户分配特定项目的管理员分配权限.prefix.path - 设置Portal挂载到nginx/slb后的相对路径
如果希望在Portal前挂软负载,一般情况下建议直接使用根目录来挂载,不过如果有些情况希望和其它应用共用nginx/slb,需要加上相对路径,那么可以配置此项,如
prefix.path=/apollo
,更多信息可以参考Portal挂载到nginx/slb后如何设置相对路径?。
4.2.2 调整ApolloConfigDB配置
配置项统一存储在ApolloConfigDB.ServerConfig表中,需要注意每个环境的ApolloConfigDB.ServerConfig都需要单独配置,修改完一分钟实时生效。
1. eureka.service.url - Eureka服务Url
不管是apollo-configservice还是apollo-adminservice都需要向eureka服务注册,所以需要配置eureka服务地址。按照目前的实现,apollo-configservice本身就是一个eureka服务,所以只需要填入apollo-configservice的地址即可,如有多个,用逗号分隔(注意不要忘了/eureka/后缀)。
需要注意的是每个环境只填入自己环境的eureka服务地址,比如FAT的apollo-configservice是1.1.1.1:8080和2.2.2.2:8080,UAT的apollo-configservice是3.3.3.3:8080和4.4.4.4:8080,PRO的apollo-configservice是5.5.5.5:8080和6.6.6.6:8080,那么:
在FAT环境的ApolloConfigDB.ServerConfig表中设置eureka.service.url为:
http://1.1.1.1:8080/eureka/,http://2.2.2.2:8080/eureka/
在UAT环境的ApolloConfigDB.ServerConfig表中设置eureka.service.url为:
http://3.3.3.3:8080/eureka/,http://4.4.4.4:8080/eureka/
在PRO环境的ApolloConfigDB.ServerConfig表中设置eureka.service.url为:
http://5.5.5.5:8080/eureka/,http://6.6.6.6:8080/eureka/
注意:这里需要填写本环境中全部的eureka服务地址,因为eureka需要互相复制注册信息
如果希望将Config Service和Admin Service注册到公司统一的Eureka上,可以参考部署&开发遇到的常见问题 - 将Config Service和Admin Service注册到单独的Eureka Server上章节
在多机房部署时,往往希望config service和admin service只向同机房的eureka注册,要实现这个效果,需要利用ServerConfig
表中的cluster字段,config service和admin service会读取所在机器的/opt/settings/server.properties
(Mac/Linux)或C:\opt\settings\server.properties
(Windows)中的idc属性,如果该idc有对应的eureka.service.url配置,那么就只会向该机房的eureka注册。比如config service和admin service会部署到SHAOY
和SHAJQ
两个IDC,那么为了实现这两个机房中的服务只向该机房注册,那么可以在ServerConfig
表中新增两条记录,分别填入SHAOY
和SHAJQ
两个机房的eureka地址即可,default
cluster的记录可以保留,如果有config service和admin service不是部署在SHAOY
和SHAJQ
这两个机房的,就会使用这条默认配置。
Key | Cluster | Value | Comment |
---|---|---|---|
eureka.service.url | default | http://1.1.1.1:8080/eureka/ | 默认的Eureka服务Url |
eureka.service.url | SHAOY | http://2.2.2.2:8080/eureka/ | SHAOY的Eureka服务Url |
eureka.service.url | SHAJQ | http://3.3.3.3:8080/eureka/ | SHAJQ的Eureka服务Url |
2.namespace.lock.switch - 一次发布只能有一个人修改开关,用于发布审核
这是一个功能开关,如果配置为true的话,那么一次配置发布只能是一个人修改,另一个发布。
生产环境建议开启此选项
3. config-service.cache.enabled - 是否开启配置缓存
这是一个功能开关,如果配置为true的话,config service会缓存加载过的配置信息,从而加快后续配置获取性能。
默认为false,开启前请先评估总配置大小并调整config service内存配置。
开启缓存后必须确保应用中配置的app.id大小写正确,否则将获取不到正确的配置
4. item.key.length.limit - 配置项 key 最大长度限制
默认配置是128。
5. item.value.length.limit - 配置项 value 最大长度限制
默认配置是20000。
4.3 获取安装包
可以通过两种方式获取安装包:
直接下载安装包
从GitHub Release页面下载预先打好的安装包
如果对Apollo的代码没有定制需求,建议使用这种方式,可以省去本地打包的过程
通过源码构建
从GitHub Release页面下载Source code包或直接clone源码后在本地构建
如果需要对Apollo的做定制开发,需要使用这种方式
4.3.1 直接下载安装包
1.获取安装包
从GitHub Release页面下载最新版本的apollo-configservice-x.x.x-github.zip
、apollo-adminservice-x.x.x-github.zip
和apollo-portal-x.x.x-github.zip
即可。
2.配置数据库连接信息
Apollo服务端需要知道如何连接到你前面创建的数据库,数据库连接串信息位于上一步下载的压缩包中的config/application-github.properties
中。
2.1 配置apollo-configservice的数据库连接信息
spring.datasource.url = jdbc:mysql://localhost:3306/ApolloConfigDB?useSSL=false&characterEncoding=utf8
spring.datasource.username = someuser
spring.datasource.password = somepwd
注:由于ApolloConfigDB在每个环境都有部署,所以对不同的环境config-service需要配置对应环境的数据库参数
2.2 配置apollo-adminservice的数据库连接信息
# DataSource
spring.datasource.url = jdbc:mysql://localhost:3306/ApolloConfigDB?useSSL=false&characterEncoding=utf8
spring.datasource.username = someuser
spring.datasource.password = somepwd
注:由于ApolloConfigDB在每个环境都有部署,所以对不同的环境admin-service需要配置对应环境的数据库参数
2.3 配置apollo-portal的数据库连接信息
# DataSource
spring.datasource.url = jdbc:mysql://localhost:3306/ApolloPortalDB?useSSL=false&characterEncoding=utf8
spring.datasource.username = someuser
spring.datasource.password = somepwd
2.4 配置apollo-portal的meta service信息
Apollo Portal需要在不同的环境访问不同的meta service(apollo-configservice)地址,所以我们需要在配置中提供这些信息。默认情况下,meta service和config service是部署在同一个JVM进程,所以meta service的地址就是config service的地址。
打开apollo-portal-x.x.x-github.zip
中config
目录下的apollo-env.properties
文件。
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
除了通过apollo-env.properties
方式配置meta service以外,apollo也支持在运行时指定meta service(优先级比apollo-env.properties
高):
通过Java System Property
${env}_meta
可以通过Java的System Property
${env}_meta
来指定,如java -Ddev_meta=http://config-service-url -jar xxx.jar
也可以通过程序指定,如
System.setProperty("dev_meta", "http://config-service-url");
通过操作系统的System Environment
${ENV}_META
如
DEV_META=http://config-service-url
注意key为全大写,且中间是
_
分隔
注1: 为了实现meta service的高可用,推荐通过SLB(Software Load Balancer)做动态负载均衡
注2: meta service地址也可以填入IP,0.11.0版本之前只支持填入一个IP。从0.11.0版本开始支持填入以逗号分隔的多个地址(PR #1214),如
http://1.1.1.1:8080,http://2.2.2.2:8080
,不过生产环境还是建议使用域名(走slb),因为机器扩容、缩容等都可能导致IP列表的变化。
4.3.2 通过源码构建
1.配置数据库连接信息
Apollo服务端需要知道如何连接到你前面创建的数据库,所以需要编辑scripts/build.sh,修改ApolloPortalDB和ApolloConfigDB相关的数据库连接串信息。
注意:填入的用户需要具备对ApolloPortalDB和ApolloConfigDB数据的读写权限。
#apollo config db info
apollo_config_db_url=jdbc:mysql://localhost:3306/ApolloConfigDB?useSSL=false&characterEncoding=utf8
apollo_config_db_username=用户名
apollo_config_db_password=密码(如果没有密码,留空即可)
# apollo portal db info
apollo_portal_db_url=jdbc:mysql://localhost:3306/ApolloPortalDB?useSSL=false&characterEncoding=utf8
apollo_portal_db_username=用户名
apollo_portal_db_password=密码(如果没有密码,留空即可)
注1:由于ApolloConfigDB在每个环境都有部署,所以对不同的环境config-service和admin-service需要使用不同的数据库参数打不同的包,portal只需要打一次包即可
注2:如果不想config-service和admin-service每个环境打一个包的话,也可以通过运行时传入数据库连接串信息实现,具体可以参考 Issue 869
注3:每个环境都需要独立部署一套config-service、admin-service和ApolloConfigDB
2.配置各环境meta service地址
编辑scripts/build.sh,如下修改各环境meta service服务地址,格式为${env}_meta=http://${config-service-url:port}
,如果某个环境不需要,也可以直接删除对应的配置项:
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_SERVERS_OPTS="-Ddev_meta=$dev_meta -Dfat_meta=$fat_meta -Duat_meta=$uat_meta -Dpro_meta=$pro_meta"
除了在打包时配置meta service以外,apollo也支持在运行时指定meta service:
通过Java System Property
${env}_meta
可以通过Java的System Property
${env}_meta
来指定,如java -Ddev_meta=http://config-service-url -jar xxx.jar
也可以通过程序指定,如
System.setProperty("dev_meta", "http://config-service-url");
通过操作系统的System Environment
${ENV}_META
如
DEV_META=http://config-service-url
注意key为全大写,且中间是
_
分隔
注1: 为了实现meta service的高可用,推荐通过SLB(Software Load Balancer)做动态负载均衡
注2: meta service地址也可以填入IP,0.11.0版本之前只支持填入一个IP。从0.11.0版本开始支持填入以逗号分隔的多个地址(PR #1214),如
http://1.1.1.1:8080,http://2.2.2.2:8080
,不过生产环境还是建议使用域名(走slb),因为机器扩容、缩容等都可能导致IP列表的变化。
4.3.3 执行编译、打包
做完上述配置后,就可以执行编译和打包了。
注:初次编译会从Maven中央仓库下载不少依赖,如果网络情况不佳时很容易出错,建议使用国内的Maven仓库源,比如阿里云Maven镜像
./build.sh
该脚本会依次打包apollo-configservice, apollo-adminservice, apollo-portal。
注:由于ApolloConfigDB在每个环境都有部署,所以对不同环境的config-service和admin-service需要使用不同的数据库连接信息打不同的包,portal只需要打一次包即可。
获取apollo-configservice安装包
位于
apollo-configservice/target/
目录下的apollo-configservice-x.x.x-github.zip
需要注意的是由于ApolloConfigDB在每个环境都有部署,所以对不同环境的config-service需要使用不同的数据库参数打不同的包后分别部署
获取apollo-adminservice安装包
位于
apollo-adminservice/target/
目录下的apollo-adminservice-x.x.x-github.zip
需要注意的是由于ApolloConfigDB在每个环境都有部署,所以对不同环境的admin-service需要使用不同的数据库参数打不同的包后分别部署
获取apollo-portal安装包
位于
apollo-portal/target/
目录下的apollo-portal-x.x.x-github.zip
4.4 部署apollo服务端
4.4.1 部署apollo-configservice
将对应环境的apollo-configservice-x.x.x-github.zip上传到服务器上,解压后执行scripts/startup.sh即可。如需停止服务,执行scripts/shutdown.sh
记得在scripts/startup.sh中按照实际的环境设置一个JVM内存,以下是我们的默认设置,供参考:
export JAVA_OPTS="-server -Xms6144m -Xmx6144m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=4096m -XX:MaxNewSize=4096m -XX:SurvivorRatio=18"
注1:如果需要修改JVM参数,可以修改scripts/startup.sh的
JAVA_OPTS
部分。
注2:如要调整服务的日志输出路径,可以修改scripts/startup.sh和apollo-configservice.conf中的
LOG_DIR
。
注3:如要调整服务的监听端口,可以修改scripts/startup.sh中的
SERVER_PORT
。另外apollo-configservice同时承担meta server职责,如果要修改端口,注意要同时ApolloConfigDB.ServerConfig表中的eureka.service.url
配置项以及apollo-portal和apollo-client中的使用到的meta server信息,详见:2.2.1.2.4 配置apollo-portal的meta service信息和1.2.2 Apollo Meta Server。
注4:如果ApolloConfigDB.ServerConfig的eureka.service.url只配了当前正在启动的机器的话,在启动apollo-configservice的过程中会在日志中输出eureka注册失败的信息,如
com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused
。需要注意的是,这个是预期的情况,因为apollo-configservice需要向Meta Server(它自己)注册服务,但是因为在启动过程中,自己还没起来,所以会报这个错。后面会进行重试的动作,所以等自己服务起来后就会注册正常了。
4.4.2 部署apollo-adminservice
将对应环境的apollo-adminservice-x.x.x-github.zip
上传到服务器上,解压后执行scripts/startup.sh即可。如需停止服务,执行scripts/shutdown.sh.
记得在scripts/startup.sh中按照实际的环境设置一个JVM内存,以下是我们的默认设置,供参考:
export JAVA_OPTS="-server -Xms2560m -Xmx2560m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=1024m -XX:MaxNewSize=1024m -XX:SurvivorRatio=22"
注1:如果需要修改JVM参数,可以修改scripts/startup.sh的
JAVA_OPTS
部分。
注2:如要调整服务的日志输出路径,可以修改scripts/startup.sh和apollo-adminservice.conf中的
LOG_DIR
。
注3:如要调整服务的监听端口,可以修改scripts/startup.sh中的
SERVER_PORT
。
4.4.3 部署apollo-portal
将apollo-portal-x.x.x-github.zip
上传到服务器上,解压后执行scripts/startup.sh即可。如需停止服务,执行scripts/shutdown.sh.
记得在startup.sh中按照实际的环境设置一个JVM内存,以下是我们的默认设置,供参考:
export JAVA_OPTS="-server -Xms4096m -Xmx4096m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=1536m -XX:MaxNewSize=1536m -XX:SurvivorRatio=22"
注1:如果需要修改JVM参数,可以修改scripts/startup.sh的
JAVA_OPTS
部分。
注2:如要调整服务的日志输出路径,可以修改scripts/startup.sh和apollo-portal.conf中的
LOG_DIR
。
注3:如要调整服务的监听端口,可以修改scripts/startup.sh中的
SERVER_PORT
。
五、java客户端使用指南
5.1 准备工作
Apollo客户端依赖于AppId
,Apollo Meta Server
等环境信息来工作,所以请确保阅读下面的说明并且做正确的配置:
5.1.1 AppId
AppId是应用的身份信息,是从服务端获取配置的一个重要信息。
有以下几种方式设置,按照优先级从高到低分别为:
System Property
Apollo 0.7.0+支持通过System Property传入app.id信息,如
-Dapp.id=YOUR-APP-ID
操作系统的System Environment
Apollo 1.4.0+支持通过操作系统的System Environment APP_ID
来传入app.id信息,如
APP_ID=YOUR-APP-ID
Spring Boot application.properties
Apollo 1.0.0+支持通过Spring Boot的application.properties文件配置,如
app.id=YOUR-APP-ID
该配置方式不适用于多个war包部署在同一个tomcat的使用场景
app.properties
确保classpath:/META-INF/app.properties文件存在,并且其中内容形如:
app.id=YOUR-APP-ID
注:app.id是用来标识应用身份的唯一id,格式为string。
5.1.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信息,按照优先级从高到低分别为:
通过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");
通过Spring Boot的配置文件
可以在Spring Boot的
application.properties
或bootstrap.properties
中指定apollo.meta=http://config-service-url
该配置方式不适用于多个war包部署在同一个tomcat的使用场景
通过操作系统的System Environment
APOLLO_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
通过Java system property
${env}_meta
如果当前env是
dev
,那么用户可以配置-Ddev_meta=http://config-service-url
使用该配置方式,那么就必须要正确配置Environment,详见1.2.4.1 Environment
通过操作系统的System Environment
${ENV}_META
(1.2.0版本开始支持)
如果当前env是
dev
,那么用户可以配置操作系统的System EnvironmentDEV_META=http://config-service-url
注意key为全大写
使用该配置方式,那么就必须要正确配置Environment,详见1.2.4.1 Environment
通过
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地址
5.1.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的实现可以参考LegacyMetaServerProvider和DefaultMetaServerProvider。
5.1.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 Environment
APOLLO_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
5.1.3 本地缓存路径
Apollo客户端会把从服务端获取到的配置在本地文件系统缓存一份,用于在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置,不影响应用正常运行。
本地缓存路径默认位于以下路径,所以请确保/opt/data
或C:\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
文件内容以properties格式存储,比如如果有两个key,一个是request.timeout,另一个是batch,那么文件内容就是如下格式:
request.timeout=2000
batch=2000
5.1.3.1 自定义缓存路径
1.0.0版本开始支持以下方式自定义缓存路径,按照优先级从高到低分别为:
通过Java System Property
apollo.cacheDir
如果是运行jar文件,需要注意格式是
java -Dapollo.cacheDir=/opt/data/some-cache-dir -jar xxx.jar
可以通过Java的System Property
apollo.cacheDir
来指定在Java程序启动脚本中,可以指定
-Dapollo.cacheDir=/opt/data/some-cache-dir
也可以通过程序指定,如
System.setProperty("apollo.cacheDir", "/opt/data/some-cache-dir");
通过Spring Boot的配置文件
可以在Spring Boot的
application.properties
或bootstrap.properties
中指定apollo.cacheDir=/opt/data/some-cache-dir
通过操作系统的System Environment
APOLLO_CACHEDIR
可以通过操作系统的System Environment
APOLLO_CACHEDIR
来指定注意key为全大写,且中间是
_
分隔
通过
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都挂掉的情况下需要扩容,那么也可以先把配置从已有机器上的缓存路径复制到新机器上的相同缓存路径
5.1.4 可选配置
5.1.4.1 Environment
Environment可以通过以下3种方式的任意一个配置:
通过Java System Property
如果是运行jar文件,需要注意格式是
java -Denv=YOUR-ENVIRONMENT -jar xxx.jar
可以通过Java的System Property
env
来指定环境在Java程序启动脚本中,可以指定
-Denv=YOUR-ENVIRONMENT
注意key为全小写
通过操作系统的System Environment
还可以通过操作系统的System Environment
ENV
来指定注意key为全大写
通过配置文件
最后一个推荐的方式是通过配置文件来指定
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
5.1.4.2 Cluster(集群)
Apollo支持配置按照集群划分,也就是说对于一个appId和一个环境,对不同的集群可以有不同的配置。
1.0.0版本开始支持以下方式集群,按照优先级从高到低分别为:
通过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");
通过Spring Boot的配置文件
可以在Spring Boot的
application.properties
或bootstrap.properties
中指定apollo.cluster=SomeCluster
通过Java System Property
如果是运行jar文件,需要注意格式是
java -Didc=xxx -jar xxx.jar
可以通过Java的System Property
idc
来指定环境在Java程序启动脚本中,可以指定
-Didc=xxx
注意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
)加载配置
5.1.4.3 设置内存中的配置项是否保持和页面上的顺序一致
适用于1.6.0及以上版本
默认情况下,apollo client内存中的配置存放在Properties中(底下是Hashtable),不会刻意保持和页面上看到的顺序一致,对绝大部分的场景是没有影响的。不过有些场景会强依赖配置项的顺序(如spring cloud zuul的路由规则),针对这种情况,可以开启OrderedProperties特性来使得内存中的配置顺序和页面上看到的一致。
配置方式按照优先级从高到低分别为:
通过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");
通过Spring Boot的配置文件
可以在Spring Boot的
application.properties
或bootstrap.properties
中指定apollo.property.order.enable=true
通过
app.properties
配置文件
可以在
classpath:/META-INF/app.properties
指定apollo.property.order.enable=true
5.1.4.4 配置访问秘钥
适用于1.6.0及以上版本
Apollo从1.6.0版本开始增加访问秘钥机制,从而只有经过身份验证的客户端才能访问敏感配置。如果应用开启了访问秘钥,客户端需要配置秘钥,否则无法获取配置。
配置方式按照优先级从高到低分别为:
通过Java System Property
apollo.accesskey.secret
可以通过Java的System Property
apollo.accesskey.secret
来指定在Java程序启动脚本中,可以指定
-Dapollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719
如果是运行jar文件,需要注意格式是
java -Dapollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719 -jar xxx.jar
也可以通过程序指定,如
System.setProperty("apollo.accesskey.secret", "1cf998c4e2ad4704b45a98a509d15719");
通过Spring Boot的配置文件
可以在Spring Boot的
application.properties
或bootstrap.properties
中指定apollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719
通过操作系统的System Environment
还可以通过操作系统的System Environment
APOLLO_ACCESSKEY_SECRET
来指定注意key为全大写
通过
app.properties
配置文件
可以在
classpath:/META-INF/app.properties
指定apollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719
5.2 Maven Dependency
Apollo的客户端jar包已经上传到中央仓库,应用在实际使用时只需要按照如下方式引入即可。
<dependency>
<groupId>com.ctrip.framework.apollogroupId>
<artifactId>apollo-clientartifactId>
<version>1.1.0version>
dependency>
5.3 Springboot集成方式的客户端
支持通过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
将Apollo配置加载提到初始化日志系统之前(1.2.0+)
从1.2.0版本开始,如果希望把日志相关的配置(如logging.level.root=info
或logback-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
5.4 Spring Placeholder的使用
Spring应用通常会使用Placeholder来注入配置,使用的格式形如${someKey:someDefaultValue},如${timeout:100}。冒号前面的是key,冒号后面的是默认值。
建议在实际使用时尽量给出默认值,以免由于key没有定义导致运行时错误。
从v0.10.0开始的版本支持placeholder在运行时自动更新,具体参见PR #972。
如果需要关闭placeholder在运行时自动更新功能,可以通过以下两种方式关闭:
通过设置System Property
apollo.autoUpdateInjectedSpringProperties
,如启动时传入-Dapollo.autoUpdateInjectedSpringProperties=false
通过设置META-INF/app.properties中的
apollo.autoUpdateInjectedSpringProperties
属性,如
app.id=SampleApp
apollo.autoUpdateInjectedSpringProperties=false
5.4.1 Java Config使用方式
通过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();
}
}
5.4.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();
}
}
需要注意的是,@ConfigurationProperties
如果需要在Apollo配置变化时自动更新注入的值,需要配合使用EnvironmentChangeEvent或RefreshScope。相关代码实现,可以参考apollo-use-cases项目中的ZuulPropertiesRefresher.java和apollo-demo项目中的SampleRedisConfig.java以及SpringBootApolloRefreshConfig.java
5.4.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 - []
*
* jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}]
*/
@ApolloJsonValue("${jsonBeanProperty:[]}")
private List anotherJsonBeans;@Value("${batch:100}")private int batch;//config change listener for namespace application@ApolloConfigChangeListenerprivate void someOnChange(ConfigChangeEvent changeEvent) {//update injected value of batch if it is changed in Apolloif (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 timeoutpublic 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 abovepublic 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();
}
}
5.4.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
六、 客户端设计
上图简要描述了Apollo客户端的实现原理:
客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。(通过Http Long Polling实现)
客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。
这是一个fallback机制,为了防止推送机制失效导致配置不更新
客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property:
apollo.refreshInterval
来覆盖,单位为分钟。
客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
客户端会把从服务端获取到的配置在本地文件系统缓存一份
在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
应用程序可以从Apollo客户端获取最新的配置、订阅配置更新通知
七、 本地开发模式
Apollo客户端还支持本地开发模式,这个主要用于当开发环境无法连接Apollo服务器的时候,比如在邮轮、飞机上做相关功能开发。
在本地开发模式下,Apollo只会从本地文件读取配置信息,不会从Apollo服务器读取配置。
可以通过下面的步骤开启Apollo本地开发模式。
7.1 修改环境
修改/opt/settings/server.properties(Mac/Linux)或C:\opt\settings\server.properties(Windows)文件,设置env为Local:
env=Local
7.2 准备本地配置文件
在本地开发模式下,Apollo客户端会从本地读取文件,所以我们需要事先准备好配置文件。
7.2.1 本地配置目录
本地配置目录位于:
Mac/Linux: /opt/data/{appId}/config-cache
Windows: C:\opt\data{appId}\config-cache
appId就是应用的appId,如100004458。
请确保该目录存在,且应用程序对该目录有读权限。
【小技巧】 推荐的方式是先在普通模式下使用Apollo,这样Apollo会自动创建该目录并在目录下生成配置文件。
7.2.2 本地配置文件
本地配置文件需要按照一定的文件名格式放置于本地配置目录下,文件名格式如下:
*{appId}+{cluster}+{namespace}.properties*
appId就是应用自己的appId,如100004458
cluster就是应用使用的集群,一般在本地模式下没有做过配置的话,就是default
namespace就是应用使用的配置namespace,一般是application
文件内容以properties格式存储,比如如果有两个key,一个是request.timeout,另一个是batch,那么文件内容就是如下格式:
request.timeout=2000
batch=2000
7.2.3 修改配置
在本地开发模式下,Apollo不会实时监测文件内容是否有变化,所以如果修改了配置,需要重启应用生效。
八、测试模式
1.1.0版本开始增加了apollo-mockserver
,从而可以很好地支持单元测试时需要mock配置的场景,使用方法如下:
8.1 引入pom依赖
<dependency>
<groupId>com.ctrip.framework.apollogroupId>
<artifactId>apollo-mockserverartifactId>
<version>1.1.0version>
dependency>
8.2 在test的resources下放置mock的数据
文件名格式约定为mockdata-{namespace}.properties
8.3 写测试类
更多使用demo可以参考ApolloMockServerApiTest.java和ApolloMockServerSpringIntegrationTest.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 futureData = SettableFuture.create();
@ApolloConfigChangeListener("othernamespace")private void onChange(ConfigChangeEvent changeEvent) {
futureData.set(changeEvent);
}
}
}