Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
本文将通过一个SpringBoot的示例工程,简单介绍Apollo的基本使用方法,包括创建项目、托管和注入配置,以及配置更新事件的监听和处理。
一、环境描述
1. Apollo服务器
- 控制台:http://10.15.1.35:8070/
- 版本:0.11.0-SNAPSHOT
- 安装方法:参考《如何编排和部署容器化的Apollo服务》
2. Spring Tool Suite
- 版本:STS 3.9.2.RELEASE
3. Redis
- IP(实例-1):10.15.1.21
- 端口(实例-1):6379
- IP(实例-2):192.168.190.128
- 端口(实例-2):6379
- 安装方法:参考《Redis的Docker镜像制作详解》
二、创建和配置SpringBoot示例工程
这个示例工程在启动时会加载托管在Apollo中的配置文件,能够向指定的Redis缓存写入和读取数据,并且在修改Redis连接配置之后,不用重新启动应用,即可实现配置热更新,自动重连Redis缓存。
1. 新建示例工程
通过STS建立SpringBoot的示例工程,名称为apollo-springboot-demo
。本文采用的配置如下:
- SpringBoot版本为2.0.1
- 项目依赖关系选择Web
具体的创建步骤,本文不再详述。
2. 添加依赖关系
由于Apollo官方没有将apollo-client.jar文件上传至Maven的中央仓库,因此需要参考《如何编译安装Apollo服务器(单机版)》文档,自行编译和打包Apollo客户端的jar包文件,然后上传至公司的Maven私服。
apollo-client的依赖关系,如下图所示:
如果已经将客户端jar包上传至Maven私服,则向pom.xml
文件添加以下依赖关系:
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>0.11.0-SNAPSHOT</version>
</dependency>
如果尚未将客户端jar包上传至Maven私服,则向pom.xml
文件添加以下依赖关系:
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>0.11.0-SNAPSHOT</version>
<scope>system</scope>
<systemPath>D:\apollo-client\apollo-client.jar</systemPath>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-core</artifactId>
<version>0.11.0-SNAPSHOT</version>
<scope>system</scope>
<systemPath>D:\apollo-client\apollo-core.jar</systemPath>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
注意,apollo-client.jar和apollo-core.jar文件存放在本机的D:\apollo-client
目录之中。
3. 配置应用标识符
app.id是应用的身份信息,是从Apollo服务端获取配置的一个重要条件。在src/main/resources/META-INF
目录中创建app.properties
文件,内容如下所示:
app.id=100006666
注意,每个业务系统的app.id不能重复,并且必须和Apollo服务端创建的项目的app.id保持一致。
文件位置如下图所示:
Apollo客户端也支持通过VM运行参数传入app.id信息,例如:
-Dapp.id=100006666
本文采用前一种方式,将app.id指定为100006666
。
4. Bootstrap阶段注入配置
Apollo客户端可以将application.properties
文件中的配置托管至Apollo服务端。本文会托管SpringBoot的服务器端口号,需要在示例工程的application.properties
文件中添加以下配置:
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces=application
上述配置表示在示例工程启动的bootstrap阶段,向Spring容器注入被托管的application.properties
文件的配置信息。
注意,业务系统中的每个配置文件都对应于Apollo服务端中相应项目的命名空间(namespace),例如:application.properties文件对应于application命名空间。
5. postProcessBeanFactory阶段注入配置
在一般情况下,Apollo会在Spring的postProcessBeanFactory阶段将大多数配置注入至Spring容器中。这个阶段早于bean的初始化阶段,已经能够很好地满足普通的bean注入配置场景。
本文将Redis缓存的连接配置(IP地址和端口号)托管至Apollo服务端,并且会在示例工程启动的postProcessBeanFactory阶段注入这些配置。
在示例工程中,新建com.example.demo.config.loader
包,在该包中新建ConfigLoader
类,关键代码如下所示:
@Configuration
@EnableApolloConfig(value = "redis")
public class ConfigLoader {
}
上述类是一个空类,功能全部由@EnableApolloConfig
注解实现了。这个注解会在示例工程启动时,将Apollo服务端的redis命名空间中的配置注入Spring容器,也就相当于加载传统的redis.properties配置文件。
注意,@EnableApolloConfig
注解要和@Configuration
注解一起使用,否则不会生效。
将Redis缓存的连接配置注入Spring容器之后,还不能直接使用,需要在com.example.demo.config
包中新建JedisConfig
类,将配置信息注入这个配置类的bean,关键代码如下所示:
@Component
@ConfigurationProperties(prefix = "redis")
public class JedisConfig {
private String host;
private int port;
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
}
注意,@ConfigurationProperties
注解指定了配置项的前缀为redis
,它和这个类的实例变量拼接成具体的配置项名称,也就是redis.host
和redis.port
。
6. 监听和处理配置更新事件
当在Apollo服务端修改配置时,Apollo客户端可以监听ConfigChangeEvent事件,然后处理这个配置更新事件。在示例工程中,新建com.example.demo.callback
包,在该包中新建RedisServiceUpdateCallback
类,关键代码如下所示:
@Component
public class RedisServiceUpdateCallback {
protected static final Logger LOGGER = LoggerFactory.getLogger(RedisServiceUpdateCallback.class);
@Autowired
private RedisService redisService;
@ApolloConfig("redis")
private Config redisConfig;
@ApolloConfigChangeListener({ "redis" })
private void onRedisChange(ConfigChangeEvent changeEvent) {
String host = redisConfig.getProperty("redis.host", "10.15.1.21");
int port = redisConfig.getIntProperty("redis.port", 6379);
redisService.changeJedis(host, port);
}
}
上述代码,有三点需要注意:
@ApolloConfig
注解:将Apollo服务端的redis命名空间(也就是redis.properties文件)中的配置注入这个类的bean实例。@ApolloConfigChangeListener
注解:监听redis命名空间的配置更新事件,若该事件发生,则调用onRedisChange方法,处理该事件。ConfigChangeEvent
参数:虽然示例代码没有使用这个参数,但是它很实用,可以获取被修改配置项的key集合,以及被修改配置项的新值、旧值和修改类型等信息。
7. 其他代码
其他功能代码,本文就不再一一赘述,可以使用Git下载本示例工程,Git路径如下所示:
http://10.15.1.248/yangbin/apollo-springboot-demo.git
这些功能代码的作用,如下所示:
JedisUtil
:Jedis工具类,负责创建Redis连接。RedisService
:缓存服务类,负责向缓存写入/获取数据,以及连接配置更新时自动重连。RedisController
:缓存功能控制器类,负责接收操作请求,以及显示操作结果。ApolloSpringbootDemoApplication
:示例工程的启动类。
三、创建和配置Apollo项目
1. 登录Apollo系统
在Web浏览器中,打开Apollo服务端的URL:
http://10.15.1.35:8070/
输入默认的用户名和密码(分别是apollo和admin),登录Apollo系统,首页如下图所示:
2. 创建项目
点击“创建项目”按钮,跳转至Apollo配置中心的创建项目页面,填写各项信息,如下图所示:
输入的项目信息,如下所示:
- 部门:选择应用所在的部门。
- 应用Id:用来标识应用身份的唯一标识符,格式为字符串,需要和客户端app.properties中配置的app.id对应。
- 应用名称:应用名,仅用于界面展示。
- 应用负责人:选择的人默认会成为该项目的管理员,具备项目权限管理、集群创建、Namespace创建等权限。
项目创建成功之后,会自动跳转至项目首页,如下图所示:
注意,新项目默认会自带application命名空间(也就是application.properties文件)。
3. 添加应用服务端口配置项
在项目首页中,点击application命名空间的“新增配置”按钮,如下图所示:
然后,弹出添加配置项页面,填写各项信息,如下图所示:
最后,点击“提交”按钮,返回至项目首页,此时能够看到server.port
配置项,如下图所示:
从上图可知,server.port
配置项尚未发布,这样的配置项暂时还不能使用,稍后会发布新建的配置项。
4. 创建redis命名空间
在项目首页,点击左下方的“添加Namespace”按钮,跳转至新建Namespace页面,然后再点击“创建Namespace”按钮,填写各项信息,如下图所示:
点击“提交”按钮,保存redis命名空间,然后跳转至权限管理页面,添加修改权限和发布权限,如下图所示:
点击“返回到项目首页”按钮,返回至项目首页,如下图所示:
在项目首页中,点击redis命名空间的“新增配置”按钮,然后按照添加server.port
配置项的步骤,添加redis.host
和redis.port
配置项。redis命名空间,如下图所示:
从上图可知,redis.host
和redis.port
配置项尚未发布,这样的配置项暂时还不能使用,稍后会发布新建的配置项。
5. 发布命名空间
Apollo将配置项的修改权限和发布权限分离,可以指定给不同的用户,这样就相当于引入了配置修改评审的机制,可以有效避免误修改的问题。
新建和修改配置项之后,还需要对外发布才能使用。在项目首页中,分别点击application和redis命名空间中的“发布”按钮,如下图所示:
发布完成之后,可以看到每个配置项的发布状态都会变成“已发布”状态,此时配置项就正式生效了!
四、验证测试
1. 启动示例工程
在STS中,配置示例工程的VM启动参数,如下图所示:
此处,设置了两个参数:
-
-Denv=dev
表示连接至Apollo的DEV环境。 -
-Ddev_meta=http://10.15.1.35:8080
指定DEV环境的Apollo服务器的IP地址。
然后,点击“Run”按钮,启动示例工程。启动完成之后,可以在Apollo的项目首页中,看到两个命名空间的实例列表,如下图所示:
点击redis命名空间的“实例列表”标签,可以看到使用这个命名空间的主机实例,如下图所示:
实例列表要注意以下几点:
- IP列中的信息是实例主机的第一块非回环网卡的IP地址。
- Data Center列中的信息取自于实例主机的/opt/settings/server.properties文件中的idc配置项,本文没有设置。
- 实例列表不能实时显示连接的实例主机信息,只能显示最近一天访问过的实例主机。
2. 添加测试数据
在实例-1的Redis缓存中,添加以下K-V对:
"test_key":"XXL-JOB, HelloWorld from redis 01 ..."
在实例-2的Redis缓存中,添加以下K-V对:
"test_key":"XXL-JOB, HelloWorld from redis 02 ..."
示例工程一开始连接至实例-1的Redis缓存。
3. 访问测试数据
在Web浏览器中访问以下URL:
http://127.0.0.1:8888/get.do?key=test_key
若浏览器显示的信息如下图所示,则表示示例工程成功连接至实例-1的Redis缓存:
4. 修改Redis连接配置
在Apollo的项目首页中,将redis命名空间的redis.host
配置项的取值修改成实例-2的Redis缓存的地址,也就是192.168.190.128
,然后发布更新的配置项,如下图所示:
发布配置项之后,示例工程会监听到配置更新,然后重新连接至实例-2的Redis缓存,STS控制的日志如下图所示:
5. 再次访问测试数据
在Web浏览器中再次访问以下URL:
http://127.0.0.1:8888/get.do?key=test_key
若浏览器显示的信息如下图所示,则表示示例工程成功连接至实例-2的Redis缓存,实现了Redis连接配置的热更新和自动重连:
五、结语
本文通过示例工程介绍了Apollo的两个最主要的功能,如下所示:
- 配置文件托管,包括BootStrap阶段和PostProcessBeanFactory阶段。
- 监听和处理配置更新事件。
除此之外,还有一些高级功能需要花费时间尝试和探索,它们能够进一步提高日常开发和运维/运营的效率:
- 应用内划分集群
- 灰度发布
- 配置更新审计(配置发布、回滚和发布历史)
- 多环境同步配置