文章目录
- 前言
- Apollo配置中心设置
- 配置环境(ENV)
- 创建应用
- 发布配置
- Springboot配置
- 引入Maven依赖
- 编辑配置文件
- 增加启动类和测试接口
- 测试
- 使用开发环境(ENV)配置
- 切换到生产环境(PRO)配置
- 测试配置中心断线
- 注册中心动态更新配置
- 参考
前言
参考这一篇 在Linux部署Apollo配置中心 可以搭建出一套Apollo配置中心服务,我们在这里重点看看Springboot如何整合Apollo,将配置交给配置中心管理,并在修改后及时生效到服务上。
我们模拟工作中的开发(development,DEV)和生产(production,PRO)两套环境,在下面例子中会实现不同环境下配置切换,需要参考 在Linux部署Apollo配置中心 中多环境方案提前搭建好两套环境的配置中心服务。
完整例子可以参考 GitHub - fruitbasket-litchi-apollo。
如果文章有帮助,可以随手关注、点赞、转发下,一起学习进步。
Apollo配置中心设置
配置环境(ENV)
首先我们需要通过Apollo的UI界面中【管理员工具 / 系统参数】配置DEV和PRO环境相关参数,配置完重启UI服务(Portal Service)生效,这样每个项目都会有两套隔离的环境配置。
先配置可支持的环境(Environment,ENV)列表(apollo.portal.envs)为dev,pro
,如果有更多个环境需求用逗号隔开就行。
每个环境都有单独的Meta Service、Config Service以及数据库,我们需要指定各环境Meta Service列表(apollo.portal.meta.servers),Meta Service和Config Service是在同一个服务里面,所以这里的地址就等同于Config Service地址。如:
{
"DEV":"http://node1:8080",
"PRO":"http://node2:8080"
}
创建应用
点击主页的【创建应用】按提示填写,需要注意的是应用唯一标识(AppId)在后面应用中会使用到,像部门、负责人、管理员等是权限管理相关信息,在Springboot配置中暂时不会涉及。
点击【提交】后点开项目可以看到配置界面,左边【环境列表】有我们配置的两套环境。
发布配置
我们点击【新增配置】增加我们的Springboot将使用的配置参数,比如日志级别(logging.level.root)。然后在选择集群将两个环境都勾选,这样两套环境都会增加这个配置。
集群是Apollo提供的另一个维度的配置隔离方式,对于一个appId和一个环境,对不同的集群可以有不同的配置。有需要可以参考 Apollo - 集群独立配置说明。
点击【保存】可以在项目界面看到新增的配置,点击【发布】才能生效。
为了方便测试不同环境切换,我们将DEV环境的配置进行修改,将日志级别改成DEBUG
,保存然后发布生效。
Springboot配置
引入Maven依赖
重点是apollo-client
,引入spring-boot-starter-web
为了等下提供一个HTTP接口测试。
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot-dependencies.version>2.3.12.RELEASE</spring-boot-dependencies.version>
<apollo-client.version>1.9.1</apollo-client.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>${apollo-client.version}</version>
</dependency>
</dependencies>
编辑配置文件
在resources
目录下创建apollo-env.properties
配置文件,里面内容是不同环境的Meta Service请求地址,也就是Config Service的地址。跟我么在Apollo配置中心配置的一样,只是格式有点区别。
dev.meta=http://node1:8080
pro.meta=http://node2:8080
再编辑我们的Springboot配置文件application.yml。
app:
id: fruitbasket-litchi-apollo # 应用唯一标识
apollo:
cache-dir: /opt/data/some-cache-dir # 配置缓存路径
autoUpdateInjectedSpringProperties: true # 是否开启 Spring 参数自动更新
bootstrap:
enabled: true # 是否开启 Apollo
namespaces: application # 设置命名空间
eagerLoad:
enabled: true # 饥饿加载
命名空间(apollo.bootstrap.namespaces)用于指定使用的N个配置,用逗号分隔,每个配置包含N个配置项,一个命名空间也相当于一个配置文件。这个配置项默认是application
,类型为properties
,如我们没有修改,不配置也没关系。创建项目默认有个命名空间为application
,格式是properties
,刚好跟这个配置对应,如果想自定义命名空间,参考 Apollo - 创建Namespace。
配置缓存路径(apollo.cache-dir)会将注册中心拉取的到的配置缓存在这个路径下,如果注册中心都不可用了,还能保证服务能利用本地缓存启动。每个应用会根据appId生成不同的目录,目录定义为[appId]/config-cache/
,比如fruitbasket-litchi-apollo/config-cache/
。每个命名空间都会生成一个缓存文件保存相关配置项,文件名定义为[appId]-[cluster]-[命名空间+类型]
,比如fruitbasket-litchi-apollo+default+application.properties
。里面内容主要是配置信息,比如:
#Persisted by DefaultConfig
#Wed Dec 08 13:28:43 CST 2021
logging.level.root=DEBUG
饥饿加载(apollo.bootstrap.eagerLoad.enabled)可以让系统初始化之前从Apollo加载配置。如果希望把日志相关的配置(如logging.level.root=info
或logback-spring.xml
中的参数)也放在Apollo管理,就需要开启这个使Apollo的加载顺序放到日志系统加载之前。
增加启动类和测试接口
增加一个Springboot启动类,顺便增加一个Controller接口进行测试。接口返回项目使用的环境(env)和我们配置的日志级别(logging.level.root),同时打印DEBUG
级别日志。
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableApolloConfig
@SpringBootApplication
public class ApolloApplication {
private static final Logger logger = LoggerFactory.getLogger(ApolloApplication.class);
public static void main(String[] args) {
SpringApplication.run(ApolloApplication.class, args);
}
@Autowired
private Environment environment;
@GetMapping
public String test() {
String ret = String.format("env=%s, logging.level.root=%s"
, environment.getProperty("env"), environment.getProperty("logging.level.root"));
logger.debug(ret);
return ret;
}
}
最后在启动的时候通过JVM启动参数来指定使用的环境,格式如-Denv=DEV
。如果使用IDEA启动服务,可以点击【Edit Configurations】,填在VM Options
中。
其实还有其他多种方式指定环境或者直接指定Meta Service地址,可以参考使用的 Apollo - 客户端使用指南。
测试
使用开发环境(ENV)配置
我们先用开发环境(DEV)配置启动项目,在控制可以看到按我们DEV环境配置输出了DEBUG
级别的日志。
其中有段黄色的警告,是说我们没有通过这4种可行方式指定Meta Service地址,因为我们配置方式不是直接指定的,而是通过统一配置到apollo-env.properties
中,然后在启动项中指明使用的环境,结果是一样的,可以不用理会。
然后通过[IP]:[端口号]
访问我们的测试接口可以看到返回的环境是DEV
,日志级别为DEBUG
。
切换到生产环境(PRO)配置
将JVM启动参数改为-Denv=PRO
重启,访问测试接口可以看到配置切换过来了,控制台也不再输出DEBUG
级别日志。
测试配置中心断线
让生产环境(PRO)配置中心停机,或者让Springboot服务网络断开访问不到生产环境注册中心。我们从启动日志可以看到,应用初始化前就警告无法同步配置(Sync config from … failed),连接被拒绝(Connection refused)。
后面服务不断重试,从1秒开始。每次时间间隔为前一次的2倍。
间隔时间到了120秒就不再增长了。
但是我们的测试接口还是能正常访问的,获取到的配置是最后一次生效的环境配置PRO
。我们在本地缓存文件fruitbasket-litchi-apollo+default+application.properties
中能看到缓存的是PRO环境的配置。
如果我们重启配置中心,或者恢复网络,服务会重试成功不再发出警告。
注册中心动态更新配置
我们修改正在使用的生产环境(PRO)配置,将日志级别由INFO
改成DEBUG
,然后点【发布】。
重新访问接口可以看到日志级别配置已经改变为DEBUG
。但是控制台不会输出测试接口的DEBUG
日志,日志系统加载完后修改配置是没用的。在Springboot中本身有很多参数在运行中变更不能生效。
点击【回滚】可以让配置更新到上一个版本,点击发布历史列出之前的变动,然后可以指定回滚到某个版本中。
至于发布配置实时更新的流程可以参考 Apollo - 配置发布后的实时推送设计。
大致流程是用户通过IU界面(Portal Service)修改发布配置,修改请求发送到(Admin Service)写到数据库表ReleaseMessage
中,Config Service每秒定时去扫描消费这个表中的发布消息(Admin Service和Config Service是使用同一个库),最后通知给客户端(Springboot)。
客户端定时向Config Service接口notifications/v2
发起HTTP请求,如果有客户端关心的配置发布,则返回相关的命名空间(Namespace)信息,客户端再发起请求获取该命名空间的最新配置,并更新且缓存到本地。
如果定时拉取配置的请求时,没有客户端关心的配置发布,那保存连接60秒,中间有发布则返回,否则超时返回状态码304。这点跟消息队列RocketMQ、Kafka的长轮询机制差不多。
参考
GitHub - fruitbasket-litchi-apollo