Springboot整合Apollo配置中心

发布时间 2023-04-05 20:09:09作者: 我有八千部下

前言

参考这一篇 在Linux部署Apollo配置中心 可以搭建出一套Apollo配置中心服务,我们在这里重点看看Springboot如何整合Apollo,将配置交给配置中心管理,并在修改后及时生效到服务上。

我们模拟工作中的开发(development,DEV)和生产(production,PRO)两套环境,在下面例子中会实现不同环境下配置切换,需要参考 在Linux部署Apollo配置中心 中多环境方案提前搭建好两套环境的配置中心服务。

完整例子可以参考 GitHub - fruitbasket-litchi-apollo

如果文章有帮助,可以随手关注、点赞、转发下,一起学习进步。

Apollo配置中心设置

配置环境(ENV)

首先我们需要通过Apollo的UI界面中【管理员工具 / 系统参数】配置DEV和PRO环境相关参数,配置完重启UI服务(Portal Service)生效,这样每个项目都会有两套隔离的环境配置。

image-20211208113624886

先配置可支持的环境(Environment,ENV)列表(apollo.portal.envs)为dev,pro,如果有更多个环境需求用逗号隔开就行。

image-20211208105118201

每个环境都有单独的Meta Service、Config Service以及数据库,我们需要指定各环境Meta Service列表(apollo.portal.meta.servers),Meta Service和Config Service是在同一个服务里面,所以这里的地址就等同于Config Service地址。如:

{
"DEV":"http://node1:8080",
"PRO":"http://node2:8080"
}

image-20211208114338317

创建应用

点击主页的【创建应用】按提示填写,需要注意的是应用唯一标识(AppId)在后面应用中会使用到,像部门、负责人、管理员等是权限管理相关信息,在Springboot配置中暂时不会涉及。

image-20211208110827196

image-20211208110918181

点击【提交】后点开项目可以看到配置界面,左边【环境列表】有我们配置的两套环境。

image-20211208111008704

发布配置

image-20211208112141970

我们点击【新增配置】增加我们的Springboot将使用的配置参数,比如日志级别(logging.level.root)。然后在选择集群将两个环境都勾选,这样两套环境都会增加这个配置。

集群是Apollo提供的另一个维度的配置隔离方式,对于一个appId和一个环境,对不同的集群可以有不同的配置。有需要可以参考 Apollo - 集群独立配置说明

image-20211208111309122

点击【保存】可以在项目界面看到新增的配置,点击【发布】才能生效。

image-20211208111436680

为了方便测试不同环境切换,我们将DEV环境的配置进行修改,将日志级别改成DEBUG,保存然后发布生效。

image-20211208132922396

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=infologback-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中,然后在启动项中指明使用的环境,结果是一样的,可以不用理会。

image-20211208145547137

然后通过[IP]:[端口号]访问我们的测试接口可以看到返回的环境是DEV,日志级别为DEBUG

image-20211208144821321

切换到生产环境(PRO)配置

将JVM启动参数改为-Denv=PRO重启,访问测试接口可以看到配置切换过来了,控制台也不再输出DEBUG级别日志。

image-20211208145734001

测试配置中心断线

让生产环境(PRO)配置中心停机,或者让Springboot服务网络断开访问不到生产环境注册中心。我们从启动日志可以看到,应用初始化前就警告无法同步配置(Sync config from ... failed),连接被拒绝(Connection refused)。

image-20211208150833089

后面服务不断重试,从1秒开始。每次时间间隔为前一次的2倍。

image-20211208150903530

间隔时间到了120秒就不再增长了。

image-20211208151312969

但是我们的测试接口还是能正常访问的,获取到的配置是最后一次生效的环境配置PRO。我们在本地缓存文件fruitbasket-litchi-apollo+default+application.properties中能看到缓存的是PRO环境的配置。

如果我们重启配置中心,或者恢复网络,服务会重试成功不再发出警告。

注册中心动态更新配置

我们修改正在使用的生产环境(PRO)配置,将日志级别由INFO改成DEBUG,然后点【发布】。

image-20211208153319711

重新访问接口可以看到日志级别配置已经改变为DEBUG。但是控制台不会输出测试接口的DEBUG日志,日志系统加载完后修改配置是没用的。在Springboot中本身有很多参数在运行中变更不能生效。

image-20211208153654089

点击【回滚】可以让配置更新到上一个版本,点击发布历史列出之前的变动,然后可以指定回滚到某个版本中。

image-20211208155405715

至于发布配置实时更新的流程可以参考 Apollo - 配置发布后的实时推送设计

大致流程是用户通过IU界面(Portal Service)修改发布配置,修改请求发送到(Admin Service)写到数据库表ReleaseMessage中,Config Service每秒定时去扫描消费这个表中的发布消息(Admin Service和Config Service是使用同一个库),最后通知给客户端(Springboot)。

客户端定时向Config Service接口notifications/v2发起HTTP请求,如果有客户端关心的配置发布,则返回相关的命名空间(Namespace)信息,客户端再发起请求获取该命名空间的最新配置,并更新且缓存到本地。

如果定时拉取配置的请求时,没有客户端关心的配置发布,那保存连接60秒,中间有发布则返回,否则超时返回状态码304。这点跟消息队列RocketMQ、Kafka的长轮询机制差不多。

release-message-notification-design

参考

Apollo - 中文文档

Apollo - 使用指南

Apollo - Java客户端使用指南

GitHub - fruitbasket-litchi-apollo